Я читал множество ответов StackOverflow о том, как перемещать объект, перетаскивая его по экрану. Некоторые используют тесты попадания для .featurePoints, некоторые используют перевод жестов или просто отслеживают lastPosition объекта. Но, честно говоря ... ни один не работает так, как все ожидают.
Проверка попадания по .featurePoints просто заставляет объект прыгать вокруг, потому что вы не всегда попадаете в особую точку при перемещении пальца. Я не понимаю, почему все продолжают это предлагать.
Решения вроде этого работают: Перетаскивание SCNNode в ARKit с помощью SceneKit
Но объект на самом деле не следует за вашим пальцем, и в тот момент, когда вы делаете несколько шагов или меняете угол объекта или камеры ... и пытаетесь переместить объект ... x, z все инвертируются ... и это имеет смысл сделать это.
Я действительно хочу перемещать объекты так же хорошо, как Apple Demo, но я смотрю на код от Apple ... и он безумно странный и слишком сложный, я даже немного не могу понять. Их техника столь красивого перемещения объекта даже не близка к тому, что все предлагают в Интернете. https://developer.apple.com/documentation/arkit/handling_3d_interaction_and_ui_controls_in_augmented_reality
Должен быть способ сделать это проще.





Вид позднего ответа, но я знаю, что у меня тоже были проблемы с этим. В конце концов я нашел способ сделать это, выполнив два отдельных теста попадания всякий раз, когда вызывается мой распознаватель жестов.
Во-первых, я провожу тест на попадание для своего 3d-объекта, чтобы определить, нажимаю ли я объект в данный момент или нет (так как вы получите результаты при нажатии featurePoints, плоскостей и т. д., Если вы не укажете никаких параметров). Я делаю это, используя значение .categoryBitMask для SCNHitTestOption.
Имейте в виду, что вы должны заранее назначить правильное значение .categoryBitMask вашему объектному узлу и всем его дочерним узлам, чтобы проверка попадания работала. Я объявляю перечисление, которое я могу использовать для этого:
enum BodyType : Int {
case ObjectModel = 2;
}
Как становится очевидным из ответа на мой вопрос о значениях .categoryBitMask, которые я опубликовал здесь, важно учитывать, какие значения вы назначаете своей битовой маске.
Ниже приведен код, который я использую вместе с UILongPressGestureRecognizer в
чтобы выбрать объект, который я нажимаю сейчас:
guard let recognizerView = recognizer.view as? ARSCNView else { return }
let touch = recognizer.location(in: recognizerView)
let hitTestResult = self.sceneView.hitTest(touch, options: [SCNHitTestOption.categoryBitMask: BodyType.ObjectModel.rawValue])
guard let modelNodeHit = hitTestResult.first?.node else { return }
После этого я провожу второе испытание, чтобы найти самолет, на который я направляюсь.
Вы можете использовать тип .existingPlaneUsingExtent, если вы не хотите перемещать ваш объект дальше края плоскости, или .existingPlane, если вы хотите перемещать ваш объект на неопределенное время вдоль обнаруженной плоской поверхности.
var planeHit : ARHitTestResult!
if recognizer.state == .changed {
let hitTestPlane = self.sceneView.hitTest(touch, types: .existingPlane)
guard hitTestPlane.first != nil else { return }
planeHit = hitTestPlane.first!
modelNodeHit.position = SCNVector3(planeHit.worldTransform.columns.3.x,modelNodeHit.position.y,planeHit.worldTransform.columns.3.z)
}else if recognizer.state == .ended || recognizer.state == .cancelled || recognizer.state == .failed{
modelNodeHit.position = SCNVector3(planeHit.worldTransform.columns.3.x,modelNodeHit.position.y,planeHit.worldTransform.columns.3.z)
}
Я сделал Репозиторий GitHub, когда пробовал это, а также экспериментировал с ARAnchors. Вы можете проверить это, если хотите увидеть мой метод на практике, но я делал это не для того, чтобы кто-то еще использовал его, поэтому он еще не закончен. Кроме того, ветвь разработки должна поддерживать некоторые функции для объекта с большим количеством дочерних узлов.
Обновлено: ==================================
Для пояснения, если вы хотите использовать объект .scn вместо обычной геометрии, вам необходимо выполнить итерацию по всем дочерним узлам объекта при его создании, задав битовую маску каждого дочернего объекта следующим образом:
let objectModelScene = SCNScene(named:
"art.scnassets/object/object.scn")!
let objectNode = objectModelScene.rootNode.childNode(
withName: "theNameOfTheParentNodeOfTheObject", recursively: true)
objectNode.categoryBitMask = BodyType.ObjectModel.rawValue
objectNode.enumerateChildNodes { (node, _) in
node.categoryBitMask = BodyType.ObjectModel.rawValue
}
Затем в распознавателе жестов после получения hitTestResult
let hitTestResult = self.sceneView.hitTest(touch, options: [SCNHitTestOption.categoryBitMask: BodyType.ObjectModel.rawValue])
вам нужно найти родительский узел, так как в противном случае вы можете перемещать отдельный дочерний узел, который вы только что нажали. Сделайте это путем рекурсивного поиска вверх по дереву узлов только что найденного узла.
guard let objectNode = getParentNodeOf(hitTestResult.first?.node) else { return }
где вы объявляете метод getParentNode следующим образом
func getParentNodeOf(_ nodeFound: SCNNode?) -> SCNNode? {
if let node = nodeFound {
if node.name == "theNameOfTheParentNodeOfTheObject" {
return node
} else if let parent = node.parent {
return getParentNodeOf(parent)
}
}
return nil
}
Затем вы можете выполнять любую операцию с objectNode, поскольку он будет родительским узлом вашего объекта .scn, а это означает, что любое преобразование, примененное к нему, также будет применено к дочерним узлам.
Он отлично работает с объектом 3D-геометрии, но как я могу это сделать, добавив файл сцены 3D-объекта (.scn). Я пробовал, но не работает. Если вы пробовали использовать файл .scn, он будет мне полезен.
Вы должны перебрать все узлы объекта scn (родительский и все дочерние узлы), задав битовую маску каждого узла. Это связано с тем, что в противном случае при нажатии на экран вы могли бы выбрать узел без правильной битовой маски.
При касании я добавляю узел следующим образом: guard let frameScene = SCNScene (с именем: "art.scnassets / vase / vase.scn"), let frameNode = frameScene.rootNode.childNode (withName: "vase", рекурсивно: true) else {return nil} frameNode.categoryBitMask = BodyType.ObjectModel.rawValue вернуть frameNode
Да, вы устанавливаете только битовую маску родительского узла. Попробуйте добавить это: frameNode.enumerateChildNodes {(node, _) в node.categoryBitMask = BodyType.ObjectModel.rawValue}. Кроме того, когда вы получаете hitTestResult от modelNodeHit, вам нужно будет рекурсивно выполнить итерацию до родительского узла. Если вы посмотрите на ветку разработки репозитория github, который я опубликовал, вы найдете нужные вам методы.
При добавлении этого ваза перемещается, но узлы цветов и листьев разбрасываются. Некоторые движутся, но не должным образом, а некоторые не двигаются.
Проверьте мою правку в моем ответе выше, это должно помочь вам понять, что делать.
Привет, @ A.Claesson. Не могли бы вы мне помочь с этим stackoverflow.com/questions/63251149/…
Я добавил некоторые из своих идей к ответу Клаессона. Я заметил некоторую задержку при перетаскивании узла. Я обнаружил, что узел не может следовать за движением пальца.
Чтобы узел перемещался более плавно, я добавил переменную, которая отслеживает узел, который в настоящее время перемещается, и установил положение в соответствии с местом касания.
var selectedNode: SCNNode?
Кроме того, я установил значение .categoryBitMask, чтобы указать категорию узлов, которые я хочу отредактировать (переместить). Значение битовой маски по умолчанию - 1.
Причина, по которой мы устанавливаем битовую маску категории, заключается в том, чтобы различать разные типы узлов и указывать те, которые вы хотите выбрать (для перемещения и т. д.).
enum CategoryBitMask: Int {
case categoryToSelect = 2 // 010
case otherCategoryToSelect = 4 // 100
// you can add more bit masks below . . .
}
Затем я добавил UILongPressGestureRecognizer в viewDidLoad().
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed))
self.sceneView.addGestureRecognizer(longPressRecognizer)
Ниже приведен UILongPressGestureRecognizer, который я использовал для обнаружения длительного нажатия, которое инициирует перетаскивание узла.
Сначала достаньте сенсорный location от recognizerView
@objc func longPressed(recognizer: UILongPressGestureRecognizer) {
guard let recognizerView = recognizer.view as? ARSCNView else { return }
let touch = recognizer.location(in: recognizerView)
Следующий код запускается один раз при обнаружении длительного нажатия.
Здесь мы выполняем hitTest, чтобы выбрать затронутый узел. Обратите внимание, что здесь мы указываем опцию .categoryBitMask, чтобы выбрать только узлы из следующей категории: CategoryBitMask.categoryToSelect
// Runs once when long press is detected.
if recognizer.state == .began {
// perform a hitTest
let hitTestResult = self.sceneView.hitTest(touch, options: [SCNHitTestOption.categoryBitMask: CategoryBitMask.categoryToSelect])
guard let hitNode = hitTestResult.first?.node else { return }
// Set hitNode as selected
self.selectedNode = hitNode
Следующий код будет запускаться периодически, пока пользователь не отпустит палец.
Здесь мы выполняем еще один hitTest, чтобы получить плоскость, по которой должен двигаться узел.
// Runs periodically after .began
} else if recognizer.state == .changed {
// make sure a node has been selected from .began
guard let hitNode = self.selectedNode else { return }
// perform a hitTest to obtain the plane
let hitTestPlane = self.sceneView.hitTest(touch, types: .existingPlane)
guard let hitPlane = hitTestPlane.first else { return }
hitNode.position = SCNVector3(hitPlane.worldTransform.columns.3.x,
hitNode.position.y,
hitPlane.worldTransform.columns.3.z)
Убедитесь, что вы отменили выбор узла, когда убрали палец с экрана.
// Runs when finger is removed from screen. Only once.
} else if recognizer.state == .ended || recognizer.state == .cancelled || recognizer.state == .failed{
guard let hitNode = self.selectedNode else { return }
// Undo selection
self.selectedNode = nil
}
}
Короткий ответ: Чтобы получить этот красивый и плавный эффект перетаскивания, как в демонстрационном проекте Apple, вам нужно будет сделать это, как в демонстрационном проекте Apple (Обработка трехмерного взаимодействия). С другой стороны, я согласен с вами, что код может сбивать с толку, если вы посмотрите на него впервые. Совершенно непросто рассчитать правильное движение объекта, размещенного на плоскости пола - всегда и из любого места или угла обзора. Это сложная конструкция кода, обеспечивающая превосходный эффект перетаскивания. Apple проделала большую работу для достижения этой цели, но не упростила нам задачу.
Полный ответ: Удаление шаблона AR Interaction для ваших нуждающихся приведет к кошмару, но оно тоже должно сработать, если вы потратите достаточно времени. Если вы предпочитаете начинать с нуля, в основном начните использовать обычный быстрый шаблон Xcode ARKit / SceneKit (тот, который содержит космический корабль).
Вам также потребуется весь проект шаблона взаимодействия AR от Apple. (Ссылка включена в вопрос SO) В конце вы должны иметь возможность перетащить что-то под названием VirtualObject, которое на самом деле является специальным узлом SCNNode. Вдобавок у вас будет красивый Focus Square, который может быть полезен для любых целей - например, для первоначального размещения объектов или добавления пола или стены. (Некоторый код для эффекта перетаскивания и использования квадрата фокуса как бы объединены или связаны вместе - выполнение этого без квадрата фокуса на самом деле будет сложнее)
Начать: Скопируйте следующие файлы из шаблона AR Interaction в пустой проект:
Добавьте UIGestureRecognizerDelegate в определение класса ViewController следующим образом:
class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
Добавьте этот код в свой ViewController.swift в разделе определений прямо перед viewDidLoad:
// MARK: for the Focus Square
// SUPER IMPORTANT: the screenCenter must be defined this way
var focusSquare = FocusSquare()
var screenCenter: CGPoint {
let bounds = sceneView.bounds
return CGPoint(x: bounds.midX, y: bounds.midY)
}
var isFocusSquareEnabled : Bool = true
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
/// The tracked screen position used to update the `trackedObject`'s position in `updateObjectToCurrentTrackingPosition()`.
private var currentTrackingPosition: CGPoint?
/**
The object that has been most recently intereacted with.
The `selectedObject` can be moved at any time with the tap gesture.
*/
var selectedObject: VirtualObject?
/// The object that is tracked for use by the pan and rotation gestures.
private var trackedObject: VirtualObject? {
didSet {
guard trackedObject != nil else { return }
selectedObject = trackedObject
}
}
/// Developer setting to translate assuming the detected plane extends infinitely.
let translateAssumingInfinitePlane = true
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
В viewDidLoad перед настройкой сцены добавьте этот код:
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
let panGesture = ThresholdPanGesture(target: self, action: #selector(didPan(_:)))
panGesture.delegate = self
// Add gestures to the `sceneView`.
sceneView.addGestureRecognizer(panGesture)
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
В самом конце вашего ViewController.swift добавьте этот код:
// MARK: - Pan Gesture Block
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
@objc
func didPan(_ gesture: ThresholdPanGesture) {
switch gesture.state {
case .began:
// Check for interaction with a new object.
if let object = objectInteracting(with: gesture, in: sceneView) {
trackedObject = object // as? VirtualObject
}
case .changed where gesture.isThresholdExceeded:
guard let object = trackedObject else { return }
let translation = gesture.translation(in: sceneView)
let currentPosition = currentTrackingPosition ?? CGPoint(sceneView.projectPoint(object.position))
// The `currentTrackingPosition` is used to update the `selectedObject` in `updateObjectToCurrentTrackingPosition()`.
currentTrackingPosition = CGPoint(x: currentPosition.x + translation.x, y: currentPosition.y + translation.y)
gesture.setTranslation(.zero, in: sceneView)
case .changed:
// Ignore changes to the pan gesture until the threshold for displacment has been exceeded.
break
case .ended:
// Update the object's anchor when the gesture ended.
guard let existingTrackedObject = trackedObject else { break }
addOrUpdateAnchor(for: existingTrackedObject)
fallthrough
default:
// Clear the current position tracking.
currentTrackingPosition = nil
trackedObject = nil
}
}
// - MARK: Object anchors
/// - Tag: AddOrUpdateAnchor
func addOrUpdateAnchor(for object: VirtualObject) {
// If the anchor is not nil, remove it from the session.
if let anchor = object.anchor {
sceneView.session.remove(anchor: anchor)
}
// Create a new anchor with the object's current transform and add it to the session
let newAnchor = ARAnchor(transform: object.simdWorldTransform)
object.anchor = newAnchor
sceneView.session.add(anchor: newAnchor)
}
private func objectInteracting(with gesture: UIGestureRecognizer, in view: ARSCNView) -> VirtualObject? {
for index in 0..<gesture.numberOfTouches {
let touchLocation = gesture.location(ofTouch: index, in: view)
// Look for an object directly under the `touchLocation`.
if let object = virtualObject(at: touchLocation) {
return object
}
}
// As a last resort look for an object under the center of the touches.
// return virtualObject(at: gesture.center(in: view))
return virtualObject(at: (gesture.view?.center)!)
}
/// Hit tests against the `sceneView` to find an object at the provided point.
func virtualObject(at point: CGPoint) -> VirtualObject? {
// let hitTestOptions: [SCNHitTestOption: Any] = [.boundingBoxOnly: true]
let hitTestResults = sceneView.hitTest(point, options: [SCNHitTestOption.categoryBitMask: 0b00000010, SCNHitTestOption.searchMode: SCNHitTestSearchMode.any.rawValue as NSNumber])
// let hitTestOptions: [SCNHitTestOption: Any] = [.boundingBoxOnly: true]
// let hitTestResults = sceneView.hitTest(point, options: hitTestOptions)
return hitTestResults.lazy.compactMap { result in
return VirtualObject.existingObjectContainingNode(result.node)
}.first
}
/**
If a drag gesture is in progress, update the tracked object's position by
converting the 2D touch location on screen (`currentTrackingPosition`) to
3D world space.
This method is called per frame (via `SCNSceneRendererDelegate` callbacks),
allowing drag gestures to move virtual objects regardless of whether one
drags a finger across the screen or moves the device through space.
- Tag: updateObjectToCurrentTrackingPosition
*/
@objc
func updateObjectToCurrentTrackingPosition() {
guard let object = trackedObject, let position = currentTrackingPosition else { return }
translate(object, basedOn: position, infinitePlane: translateAssumingInfinitePlane, allowAnimation: true)
}
/// - Tag: DragVirtualObject
func translate(_ object: VirtualObject, basedOn screenPos: CGPoint, infinitePlane: Bool, allowAnimation: Bool) {
guard let cameraTransform = sceneView.session.currentFrame?.camera.transform,
let result = smartHitTest(screenPos,
infinitePlane: infinitePlane,
objectPosition: object.simdWorldPosition,
allowedAlignments: [ARPlaneAnchor.Alignment.horizontal]) else { return }
let planeAlignment: ARPlaneAnchor.Alignment
if let planeAnchor = result.anchor as? ARPlaneAnchor {
planeAlignment = planeAnchor.alignment
} else if result.type == .estimatedHorizontalPlane {
planeAlignment = .horizontal
} else if result.type == .estimatedVerticalPlane {
planeAlignment = .vertical
} else {
return
}
/*
Plane hit test results are generally smooth. If we did *not* hit a plane,
smooth the movement to prevent large jumps.
*/
let transform = result.worldTransform
let isOnPlane = result.anchor is ARPlaneAnchor
object.setTransform(transform,
relativeTo: cameraTransform,
smoothMovement: !isOnPlane,
alignment: planeAlignment,
allowAnimation: allowAnimation)
}
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
Добавьте код Focus Square
// MARK: - Focus Square (code by Apple, some by me)
func updateFocusSquare(isObjectVisible: Bool) {
if isObjectVisible {
focusSquare.hide()
} else {
focusSquare.unhide()
}
// Perform hit testing only when ARKit tracking is in a good state.
if let camera = sceneView.session.currentFrame?.camera, case .normal = camera.trackingState,
let result = smartHitTest(screenCenter) {
DispatchQueue.main.async {
self.sceneView.scene.rootNode.addChildNode(self.focusSquare)
self.focusSquare.state = .detecting(hitTestResult: result, camera: camera)
}
} else {
DispatchQueue.main.async {
self.focusSquare.state = .initializing
self.sceneView.pointOfView?.addChildNode(self.focusSquare)
}
}
}
И добавьте некоторые функции управления:
func hideFocusSquare() { DispatchQueue.main.async { self.updateFocusSquare(isObjectVisible: true) } } // to hide the focus square
func showFocusSquare() { DispatchQueue.main.async { self.updateFocusSquare(isObjectVisible: false) } } // to show the focus square
Из VirtualObjectARView.swift КОПИЯ! всю функцию smartHitTest в ViewController.swift (поэтому они существуют дважды)
func smartHitTest(_ point: CGPoint,
infinitePlane: Bool = false,
objectPosition: float3? = nil,
allowedAlignments: [ARPlaneAnchor.Alignment] = [.horizontal, .vertical]) -> ARHitTestResult? {
// Perform the hit test.
let results = sceneView.hitTest(point, types: [.existingPlaneUsingGeometry, .estimatedVerticalPlane, .estimatedHorizontalPlane])
// 1. Check for a result on an existing plane using geometry.
if let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }),
let planeAnchor = existingPlaneUsingGeometryResult.anchor as? ARPlaneAnchor, allowedAlignments.contains(planeAnchor.alignment) {
return existingPlaneUsingGeometryResult
}
if infinitePlane {
// 2. Check for a result on an existing plane, assuming its dimensions are infinite.
// Loop through all hits against infinite existing planes and either return the
// nearest one (vertical planes) or return the nearest one which is within 5 cm
// of the object's position.
let infinitePlaneResults = sceneView.hitTest(point, types: .existingPlane)
for infinitePlaneResult in infinitePlaneResults {
if let planeAnchor = infinitePlaneResult.anchor as? ARPlaneAnchor, allowedAlignments.contains(planeAnchor.alignment) {
if planeAnchor.alignment == .vertical {
// Return the first vertical plane hit test result.
return infinitePlaneResult
} else {
// For horizontal planes we only want to return a hit test result
// if it is close to the current object's position.
if let objectY = objectPosition?.y {
let planeY = infinitePlaneResult.worldTransform.translation.y
if objectY > planeY - 0.05 && objectY < planeY + 0.05 {
return infinitePlaneResult
}
} else {
return infinitePlaneResult
}
}
}
}
}
// 3. As a final fallback, check for a result on estimated planes.
let vResult = results.first(where: { $0.type == .estimatedVerticalPlane })
let hResult = results.first(where: { $0.type == .estimatedHorizontalPlane })
switch (allowedAlignments.contains(.horizontal), allowedAlignments.contains(.vertical)) {
case (true, false):
return hResult
case (false, true):
// Allow fallback to horizontal because we assume that objects meant for vertical placement
// (like a picture) can always be placed on a horizontal surface, too.
return vResult ?? hResult
case (true, true):
if hResult != nil && vResult != nil {
return hResult!.distance < vResult!.distance ? hResult! : vResult!
} else {
return hResult ?? vResult
}
default:
return nil
}
}
Вы можете увидеть некоторые ошибки в скопированной функции в отношении hitTest. Просто исправьте это так:
hitTest... // which gives an Error
sceneView.hitTest... // this should correct it
Реализуйте функцию updateAtTime отрисовщика и добавьте следующие строки:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// For the Focus Square
if isFocusSquareEnabled { showFocusSquare() }
self.updateObjectToCurrentTrackingPosition() // *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
}
И, наконец, добавьте несколько вспомогательных функций для Focus Square.
func hideFocusSquare() { DispatchQueue.main.async { self.updateFocusSquare(isObjectVisible: true) } } // to hide the focus square
func showFocusSquare() { DispatchQueue.main.async { self.updateFocusSquare(isObjectVisible: false) } } // to show the focus square
На этом этапе вы все еще можете увидеть около дюжины ошибок и предупреждений в импортированных файлах, это может произойти, если вы делаете это в Swift 5 и у вас есть несколько файлов Swift 4. Просто позвольте Xcode исправить ошибки. (Все дело в переименовании некоторых операторов кода, Xcode знает лучше)
Зайдите в VirtualObject.swift и найдите этот блок кода:
if smoothMovement {
let hitTestResultDistance = simd_length(positionOffsetFromCamera)
// Add the latest position and keep up to 10 recent distances to smooth with.
recentVirtualObjectDistances.append(hitTestResultDistance)
recentVirtualObjectDistances = Array(recentVirtualObjectDistances.suffix(10))
let averageDistance = recentVirtualObjectDistances.average!
let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance
simdPosition = cameraWorldPosition + averagedDistancePosition
} else {
simdPosition = cameraWorldPosition + positionOffsetFromCamera
}
Измените комментарий или замените весь этот блок этой единственной строкой кода:
simdPosition = cameraWorldPosition + positionOffsetFromCamera
На этом этапе вы сможете скомпилировать проект и запустить его на устройстве. Вы должны увидеть космический корабль и желтый квадрат фокусировки, который уже должен работать.
Чтобы начать размещение объекта, который вы можете перетащить, вам понадобится функция для создания так называемого VirtualObject, как я сказал в начале.
Используйте этот пример функции для проверки (добавьте ее где-нибудь в контроллере представления):
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if focusSquare.state != .initializing {
let position = SCNVector3(focusSquare.lastPosition!)
// *** FOR OBJECT DRAGGING PAN GESTURE - APPLE ***
let testObject = VirtualObject() // give it some name, when you dont have anything to load
testObject.geometry = SCNCone(topRadius: 0.0, bottomRadius: 0.2, height: 0.5)
testObject.geometry?.firstMaterial?.diffuse.contents = UIColor.red
testObject.categoryBitMask = 0b00000010
testObject.name = "test"
testObject.castsShadow = true
testObject.position = position
sceneView.scene.rootNode.addChildNode(testObject)
}
}
Примечание: все, что вы хотите перетащить на самолет, необходимо настроить с помощью VirtualObject () вместо SCNNode (). Все остальное, что касается VirtualObject, остается таким же, как SCNNode.
(Вы также можете добавить некоторые общие расширения SCNNode, например, для загрузки сцен по имени - полезно при обращении к импортированным моделям)
Повеселись!
Привет, Вы нашли какое-либо решение или какой подход используете. Я также сталкиваюсь с такой же проблемой с жестом панорамирования. Любая помощь или направление будут оценены.