web-dev-qa-db-fra.com

ARKIT: Déplacer un objet avec PanGesture (dans le bon sens)

J'ai lu de nombreuses réponses StackOverflow sur la façon de déplacer un objet en le faisant glisser sur l'écran. Certains utilisent des tests de hit contre .featurePoints certains utilisent la traduction des gestes ou simplement en gardant une trace de la dernière position de l'objet. Mais honnêtement .. aucun ne fonctionne comme tout le monde s'attend à ce que cela fonctionne.

Les tests de frappe contre .featurePoints font simplement sauter l'objet tout autour, car vous ne touchez pas toujours un point de fonctionnalité lorsque vous faites glisser votre doigt. Je ne comprends pas pourquoi tout le monde continue de suggérer cela.

Des solutions comme celle-ci fonctionnent: Glisser SCNNode dans ARKit en utilisant SceneKit

Mais l'objet ne suit pas vraiment votre doigt, et au moment où vous faites quelques pas ou changez l'angle de l'objet ou de la caméra .. et essayez de déplacer l'objet .. les x, z sont tous inversés .. et ont un sens total pour faire ça.

Je veux vraiment déplacer des objets aussi bons que la démo Apple, mais je regarde le code d'Apple ... et c'est incroyablement bizarre et trop compliqué, je ne peux même pas comprendre un peu. Leur technique pour se déplacer l'objet si beau n'est même pas proche de ce que tout le monde propose en ligne. https://developer.Apple.com/documentation/arkit/handling_3d_interaction_and_ui_controls_in_augmented_reality

Il doit y avoir un moyen plus simple de le faire.

12
omarojo

J'ai ajouté certaines de mes idées à la réponse de Claessons. J'ai remarqué un certain décalage lors du déplacement du nœud. J'ai trouvé que le nœud ne peut pas suivre le mouvement du doigt.

Pour que le nœud se déplace plus facilement, j'ai ajouté une variable qui garde la trace du nœud qui est actuellement déplacé et définissez la position à l'emplacement du toucher.

    var selectedNode: SCNNode?

De plus, j'ai défini une valeur .categoryBitMask Pour spécifier la catégorie de nœuds que je souhaite modifier (déplacer). La valeur par défaut du masque de bits est 1.

La raison pour laquelle nous avons défini le masque de bits de catégorie est de distinguer les différents types de nœuds et de spécifier ceux que vous souhaitez sélectionner (pour vous déplacer, etc.).

    enum CategoryBitMask: Int {
        case categoryToSelect = 2        // 010
        case otherCategoryToSelect = 4   // 100
        // you can add more bit masks below . . .
    }

Ensuite, j'ai ajouté un UILongPressGestureRecognizer dans viewDidLoad().

        let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed))
        self.sceneView.addGestureRecognizer(longPressRecognizer)

Ce qui suit est le UILongPressGestureRecognizer que j'ai utilisé pour détecter un appui long, qui initie le glissement du nœud.

Tout d'abord, obtenez la touche location à partir de la recognizerView

    @objc func longPressed(recognizer: UILongPressGestureRecognizer) {

       guard let recognizerView = recognizer.view as? ARSCNView else { return }
       let touch = recognizer.location(in: recognizerView)

Le code suivant s'exécute une fois lorsqu'un appui long est détecté.

Ici, nous effectuons un hitTest pour sélectionner le nœud qui a été touché. Notez qu'ici, nous spécifions une option .categoryBitMask Pour sélectionner uniquement les nœuds de la catégorie suivante: 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

Le code suivant s'exécutera périodiquement jusqu'à ce que l'utilisateur relâche le doigt. Ici, nous effectuons un autre hitTest pour obtenir le plan sur lequel le nœud doit se déplacer.

        // 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)

Assurez-vous de désélectionner le nœud lorsque le doigt est retiré de l'écran.

        // 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
        }
    }
3
Susan Kim

Une sorte de réponse tardive mais je sais que j'ai également eu des problèmes pour résoudre ce problème. Finalement, j'ai trouvé un moyen de le faire en effectuant deux tests d'accès distincts chaque fois que mon identificateur de gestes est appelé.

Tout d'abord, j'effectue un test de réussite pour mon objet 3D pour détecter si j'appuie actuellement sur un objet ou non (comme vous obtiendriez des résultats en appuyant sur featurePoints, plans, etc. si vous ne spécifiez aucune option). Je le fais en utilisant le .categoryBitMaskvaleur de SCNHitTestOption. Gardez à l'esprit que vous devez attribuer la bonne .categoryBitMask valeur à votre nœud objet et à tous ses nœuds enfants au préalable pour que le test de hit fonctionne. Je déclare une énumération que je peux utiliser pour cela:

enum BodyType : Int {
    case ObjectModel = 2;
}

Comme le montre la réponse à ma question sur .categoryBitMask valeurs que j'ai publiées ici , il est important de considérer les valeurs que vous attribuez à votre masque binaire.

Ci-dessous, le code que j'utilise conjointement avec UILongPressGestureRecognizer afin de sélectionner l'objet sur lequel j'appuie actuellement:

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 }

Après cela, j'effectue un 2e test pour trouver un avion sur lequel j'appuie. Vous pouvez utiliser le type .existingPlaneUsingExtent si vous ne voulez pas déplacer votre objet plus loin que le bord d'un plan, ou .existingPlane si vous souhaitez déplacer votre objet indéfiniment le long d'une surface plane détectée.

 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)

 }

J'ai fait un repo GitHub quand j'ai essayé tout en expérimentant avec ARAnchors. Vous pouvez le vérifier si vous voulez voir ma méthode en pratique, mais je ne l'ai pas faite avec l'intention de quelqu'un d'autre de l'utiliser, donc c'est assez inachevé. En outre, la branche de développement doit prendre en charge certaines fonctionnalités d'un objet avec plus de childNodes.

EDIT: ==================================

Pour plus de précision, si vous souhaitez utiliser un objet .scn au lieu d'une géométrie normale, vous devez parcourir tous les nœuds enfants de l'objet lors de sa création, en définissant le masque de bits de chaque enfant comme suit:

 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
    }

Ensuite, dans le reconnaisseur de gestes après avoir obtenu un hitTestResult

let hitTestResult = self.sceneView.hitTest(touch, options: [SCNHitTestOption.categoryBitMask: BodyType.ObjectModel.rawValue])

vous devez trouver le nœud parent car sinon vous pourriez déplacer le nœud enfant individuel que vous venez d'appuyer. Pour ce faire, recherchez récursivement vers le haut dans l'arborescence des nœuds du nœud que vous venez de trouver.

guard let objectNode = getParentNodeOf(hitTestResult.first?.node) else { return }

où vous déclarez la méthode getParentNode comme suit

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
}

Ensuite, vous êtes libre d'effectuer n'importe quelle opération sur le objectNode, car ce sera le nœud parent de votre objet .scn, ce qui signifie que toute transformation qui lui est appliquée sera également appliquée aux nœuds enfants.

3
A. Claesson

Réponse courte: pour obtenir cet effet de glisser agréable et fluide comme dans le projet de démonstration Apple, vous devrez le faire comme dans le projet de démonstration Apple (manipulation Interaction 3D). D'un autre côté, je suis d'accord avec vous, que le code pourrait être déroutant si vous le regardez pour la première fois. Il n'est pas facile du tout de calculer le mouvement correct pour un objet placé sur un plan de sol - toujours et à partir de chaque emplacement ou angle de vue. C'est une construction de code complexe, qui fait ce superbe effet de glissement. Apple a fait un excellent travail pour y parvenir, mais ne nous a pas rendu la tâche trop facile .

Réponse complète: Réduire le modèle d'interaction AR pour vos résultats dans le besoin dans un cauchemar - mais devrait également fonctionner si vous investissez suffisamment de temps. Si vous préférez recommencer à zéro, commencez par utiliser un modèle Swift ARKit/SceneKit Xcode commun (celui contenant le vaisseau spatial).

Vous aurez également besoin de l'intégralité du projet de modèle d'interaction AR d'Apple. (Le lien est inclus dans la question SO) À la fin, vous devriez pouvoir faire glisser quelque chose appelé VirtualObject, qui est en fait un SCNNode spécial. De plus, vous aurez un joli carré de mise au point, qui peut être utile pour n'importe quel but - comme placer initialement des objets ou ajouter un sol ou un mur. (Certains codes pour l'effet de glissement et l'utilisation du carré de mise au point sont en quelque sorte fusionnés ou liés ensemble - le faire sans le carré de mise au point sera en fait plus compliqué)

Pour commencer: copiez les fichiers suivants du modèle AR Interaction dans votre projet vide:

  • Utilities.Swift (généralement je nomme ce fichier Extensions.Swift, il contient des extensions de base qui sont nécessaires)
  • FocusSquare.Swift
  • FocusSquareSegment.Swift
  • ThresholdPanGesture.Swift
  • VirtualObject.Swift
  • VirtualObjectLoader.Swift
  • VirtualObjectARView.Swift

Ajoutez le UIGestureRecognizerDelegate à la définition de classe ViewController comme suit:

class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {

Ajoutez ce code à votre ViewController.Swift, dans la section des définitions, juste avant 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 ***

Dans viewDidLoad, avant de configurer la scène, ajoutez ce code:

// *** 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 ***

À la toute fin de votre ViewController.Swift, ajoutez ce code:

// 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 ***

Ajouter du code carré Focus

// 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)
        }
    }
}

Et ajoutez quelques fonctions de contrôle:

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

Depuis la copie de VirtualObjectARView.Swift! la fonction entière smartHitTest au ViewController.Swift (donc ils existent deux fois)

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
    }
}

Vous pouvez voir des erreurs dans la fonction copiée concernant le hitTest. Corrigez-le comme ceci:

hitTest... // which gives an Error
sceneView.hitTest... // this should correct it

Implémentez la fonction de rendu updateAtTime et ajoutez ces lignes:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    // For the Focus Square
    if isFocusSquareEnabled { showFocusSquare() }

    self.updateObjectToCurrentTrackingPosition() // *** FOR OBJECT DRAGGING PAN GESTURE - Apple ***
}

Et enfin ajouter quelques fonctions d'assistance pour le 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

À ce stade, vous pouvez toujours voir une douzaine d'erreurs et d'avertissements dans les fichiers importés, cela peut se produire lorsque vous effectuez cette opération dans Swift 5 et que vous en avez Swift = 4 fichiers. Laissez simplement Xcode corriger les erreurs. (Il s'agit de renommer certaines instructions de code, Xcode sait mieux)

Allez dans VirtualObject.Swift et recherchez ce bloc de code:

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
}

Mettez en commentaire ou remplacez ce bloc entier par cette seule ligne de code:

simdPosition = cameraWorldPosition + positionOffsetFromCamera

À ce stade, vous devriez être en mesure de compiler le projet et de l'exécuter sur un périphérique. Vous devriez voir le vaisseau spatial et un carré de mise au point jaune qui devraient déjà fonctionner.

Pour commencer à placer un objet, que vous pouvez faire glisser, vous avez besoin d'une fonction pour créer un soi-disant VirtualObject comme je l'ai dit au début.

Utilisez cet exemple de fonction pour tester (ajoutez-le quelque part dans le contrôleur de vue):

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)
    }
}

Remarque: tout ce que vous voulez faire glisser sur un plan doit être configuré à l'aide de VirtualObject () au lieu de SCNNode (). Tout le reste concernant VirtualObject reste le même que SCNNode

(Vous pouvez également ajouter des extensions SCNNode communes, comme celle pour charger des scènes par son nom - utile lors du référencement de modèles importés)

S'amuser!

1
ZAY