首页 > 解决方案 > 我们可以在 SceneKit 中以编程方式编辑世界空间中 3D 对象的比例吗?

问题描述

我在 iOS 中使用 ARKit,我想减小现实世界中实际对象的大小。使用捏合手势我们可以调整对象的大小,但是当我点击时,我想放置尺寸减小的对象。在 SceneKit 编辑器中,如果我们选择将空间编辑到世界,则缩放选项不可用。那么是否可以以编程方式在世界空间中编辑 3D 对象的比例?

标签: iosswift3dscenekitarkit

解决方案


我大约在 1.5 年前这样做了。我不能保证它仍然有效。但首先你添加你的听众:

let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(adjustWindow))
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(adjustWindow))
    let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(adjustWindow))
    let tapGesture = UIPanGestureRecognizer(target: self, action: #selector(adjustWindow))
    pinchGesture.cancelsTouchesInView = false
    rotateGesture.cancelsTouchesInView = false
    panGesture.cancelsTouchesInView = false
    tapGesture.cancelsTouchesInView = false
    tapGesture.minimumNumberOfTouches = 2
    sceneView.addGestureRecognizer(panGesture)
    sceneView.addGestureRecognizer(pinchGesture)
    sceneView.addGestureRecognizer(rotateGesture)
    sceneView.addGestureRecognizer(tapGesture)

然后一些处理动作:

@objc func adjustWindow(_ gesture: UIGestureRecognizer) {
        // Fetch location for touch in sceneView
        let location: CGPoint = gesture.location(in: sceneView)
        // Fetch targets at the current location
        let hits = self.sceneView.hitTest(location, options: nil)
        // Check if it's a node and it's a window node that has been touched
        guard let node = hits.first?.node, node.name == "window" else {return}

        // Rotate the window if the motion is of the rotation kind
        if let rotateRecognizer = gesture as? UIRotationGestureRecognizer {
            let action = SCNAction.rotateBy(x: 0, y: rotateRecognizer.rotation/200 , z: 0, duration: 0.1)
            node.runAction(action)
            return
        }

        // Make sure user is using 1 finger to move the view
        if gesture.numberOfTouches == 1 {
            switch gesture.state {
            case .failed, .cancelled, .ended:
                startPosition = CGPoint(x: 0, y: 0)
                break
            case .began:
                // Save start position, so that we can check how far the user have moved
                startPosition = location
            case .changed:
                // Fetch new x and y position

                // Note that 1 point is 1 meter and since we want minimal movement we divide it greatly
                let deltaX = Float(location.x-startPosition.x)/400
                let deltaY = Float(location.y-startPosition.y)/20000
                // Transform and apply the new location

                // You can either move the axises of the nodes parents by using "node.scale = SCNVector(x,y,z)"
                // Or you can move the axises of the node itself by transforming the nodes reality as below
                // https://stackoverflow.com/questions/43237805/how-to-move-a-rotated-scnnode-in-scenekit
                let box = node.transform
                let translation = SCNMatrix4MakeTranslation(-deltaX, deltaY, 1)
                let newTrans = SCNMatrix4Mult(translation, box)
                node.transform = newTrans

                node.localTranslate(by: SCNVector3Make(-deltaX, deltaY, 1))
                // Set new location
                startPosition = location
            default:
                break
            }
            // Make sure user is using 2 fingers for scaling the view
        } else if gesture.numberOfTouches == 2, let pinchGesture = gesture as? UIPinchGestureRecognizer {
            switch pinchGesture.state {
            case .began:
                // Save start position, so that we can check how far the user have moved
                startPosition = location
            case .changed:

                // MARK: - Scaling
                // Scaling is unaffected as long as it scales with it's own current value hence we fetch
                // node.scale locations where we want it to be unaffected

                // Scale vertically if user moved more than 4 points

                // https://stackoverflow.com/questions/44850927/scaling-a-scnnode-in-runtime-using-the-pan-gesture
                if abs(location.y-startPosition.y) > 4 {
                    let pinchScaleY: CGFloat = pinchGesture.scale * CGFloat((node.scale.y))
                    node.scale = SCNVector3Make(node.scale.x, Float(pinchScaleY), node.scale.z)
                // Else scale horizontally, if the user moved more than 4 points horizontally
                } else if abs(location.x-startPosition.x) > 4 {
                    let pinchScaleX: CGFloat = pinchGesture.scale * CGFloat((node.scale.x))
                    node.scale = SCNVector3Make(Float(pinchScaleX), node.scale.y, node.scale.z)
                }
                pinchGesture.scale = 1
            default:
                break
            }
        } else if let panGesture = gesture as? UIPanGestureRecognizer, panGesture.numberOfTouches == 2 {
            switch panGesture.state {
            case .failed, .cancelled, .ended:
                startPosition = CGPoint(x: 0, y: 0)
                break
            case .began:
                // Save start position, so that we can check how far the user have moved
                startPosition = location
            case .changed:
                // Fetch new x and y position
                let deltaZ = Float(location.y-startPosition.y)/2000
                // Set new position
                node.position = SCNVector3Make(node.position.x, node.position.y, node.position.z+deltaZ)
                // Set new location
                startPosition = location
            default:
                break
            }
        }
    }

我知道这包含用于移动节点和旋转它的代码以及所有内容。但是捏的部分在中间: else if gesture.numberOfTouches == 2, let pinchGesture = gesture as? UIPinchGestureRecognizer


推荐阅读