在 ARKIT Swift iOS 中,如何将大型 3D .scn 文件加载到 ARSCNView 中并使其适应屏幕?

14

我正在开发使用3D模型的ARKit应用程序。因此,我使用了3D模型并添加了手势来移动、旋转和缩放3D模型。

现在我只面临一个问题,但我不确定这个问题是否相关。是3D模型存在问题还是我的程序中缺少了什么。

问题在于我使用的3D模型太大了,会超出屏幕范围。我正在尝试缩小它的大小,但它仍然很大。

以下是我的代码:

@IBOutlet var mySceneView: ARSCNView!
var selectedNode = SCNNode()
var prevLoc = CGPoint()
var touchCount : Int = 0

override func viewDidLoad() {
    super.viewDidLoad()
    self.lblTitle.text = self.sceneTitle
    let mySCN = SCNScene.init(named: "art.scnassets/\(self.sceneImagename).scn")!
    self.mySceneView.scene = mySCN

    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3Make(0, 0, 0)
    self.mySceneView.scene.rootNode.addChildNode(cameraNode)
    self.mySceneView.allowsCameraControl = true
    self.mySceneView.autoenablesDefaultLighting = true

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(detailPage.doHandleTap(_:)))
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(detailPage.doHandlePan(_:)))
    let gesturesArray = NSMutableArray()
    gesturesArray.add(tapGesture)
    gesturesArray.add(panGesture)
    gesturesArray.addObjects(from: self.mySceneView.gestureRecognizers!)
    self.mySceneView.gestureRecognizers = (gesturesArray as! [UIGestureRecognizer])
}

//MARK:- Handle Gesture
@objc func doHandlePan(_ sender: UIPanGestureRecognizer) {
    var delta = sender.translation(in: self.view)
    let loc = sender.location(in: self.view)
    if sender.state == .began {
        self.prevLoc = loc
        self.touchCount = sender.numberOfTouches
    } else if sender.state == .changed {
        delta = CGPoint(x: loc.x - prevLoc.x, y: loc.y - prevLoc.y)
        prevLoc = loc
        if self.touchCount != sender.numberOfTouches {
            return
        }

        var rotMat = SCNMatrix4()
        if touchCount == 2 {
            rotMat = SCNMatrix4MakeTranslation(Float(delta.x * 0.025), Float(delta.y * -0.025), 0)
        } else {
            let rotMatX = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.y), 1, 0, 0)
            let rotMatY = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.x), 0, 1, 0)
            rotMat = SCNMatrix4Mult(rotMatX, rotMatY)
        }

        let transMat = SCNMatrix4MakeTranslation(selectedNode.position.x, selectedNode.position.y, selectedNode.position.z)
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(transMat))

        let parentNodeTransMat = SCNMatrix4MakeTranslation((selectedNode.parent?.worldPosition.x)!, (selectedNode.parent?.worldPosition.y)!, (selectedNode.parent?.worldPosition.z)!)
        let parentNodeMatWOTrans = SCNMatrix4Mult(selectedNode.parent!.worldTransform, SCNMatrix4Invert(parentNodeTransMat))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, parentNodeMatWOTrans)

        let camorbitNodeTransMat = SCNMatrix4MakeTranslation((self.mySceneView.pointOfView?.worldPosition.x)!, (self.mySceneView.pointOfView?.worldPosition.y)!, (self.mySceneView.pointOfView?.worldPosition.z)!)
        let camorbitNodeMatWOTrans = SCNMatrix4Mult(self.mySceneView.pointOfView!.worldTransform, SCNMatrix4Invert(camorbitNodeTransMat))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(camorbitNodeMatWOTrans))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat)

        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, camorbitNodeMatWOTrans)
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(parentNodeMatWOTrans))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, transMat)
    }
}

@objc func doHandleTap(_ sender: UITapGestureRecognizer) {
    let p = sender.location(in: self.mySceneView)
    var hitResults = self.mySceneView.hitTest(p, options: nil)

    if (p.x > self.mySceneView.frame.size.width-100 || p.y < 100) {
        self.mySceneView.allowsCameraControl = !self.mySceneView.allowsCameraControl
    }

    if hitResults.count > 0 {
        let result = hitResults[0]
        let material = result.node.geometry?.firstMaterial
        selectedNode = result.node

        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.3

        SCNTransaction.completionBlock = {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.3
            SCNTransaction.commit()
        }
        material?.emission.contents = UIColor.white
        SCNTransaction.commit()
    }
}

我的问题是:

我们是否可以将任何大小的3D物体模型设置为屏幕大小,在屏幕中央自适应显示?如果有什么方法,请提供建议。

非常感谢您提供的任何指导或建议。


我仍在等待合适的解决方案。 - Mayur Prajapati
2个回答

8
你需要做的是使用 getBoundingSphereCenter 来获取包围球大小,然后可以将其投射到屏幕上。或者你可以获得该半径与scenekit相机和物体位置之间距离的比率。这样你就知道物体在屏幕上的大小。若要缩小比例,只需设置对象的 scale 属性即可。
对于第二部分,你可以使用 projectPoint

你能否详细解释一下你的答案,这样我才能得到适当的解决方案并奖励你的赏金。我很感激你的回答,但我仍然需要一个适当的解决方案,而不仅仅是理论性的,因为我还处于学习阶段,所以我对此并不是很了解。 - Mayur Prajapati
@Mayur 我建议你将问题分成两部分。首先学习如何使用 getBoundingSphereCenter,一旦掌握了这个技能,你需要想办法编写代码将其投影到屏幕上。如果你已经有了一些相关的代码,那么别人更可能会帮助你,而不是为你实现整个过程。这样说清楚了吗? - alex papa
@Mayur 我稍后会更新答案并提供更多帮助。 - alex papa
如果可能的话,你能否加入我的房间:https://chat.stackoverflow.com/rooms/99405/ios-android-react-native-apps - Mayur Prajapati
有任何适当的解决方案吗?我仍然没有得到这个适当的方案。 - Mayur Prajapati

2
"最初的回答":
我处理这个问题的方法是确保3D模型始终具有固定的尺寸。例如,如果3D模型是一个小杯子或大房子,我会确保它在场景坐标空间中始终拥有25厘米的宽度(同时保持x、y、z之间的比例关系)。你可以像这样计算节点边界框的宽度:
let mySCN = SCNScene(named: "art.scnassets/\(self.sceneImagename).scn")!

let minX = mySCN.rootNode.boundingBox.min.x
let maxX = mySCN.rootNode.boundingBox.max.x

// change 0.25 to whatever you need
// this value is in meters
let scaleValue = 0.25 / abs(minX - maxX)

// scale all axes of the node using `scaleValue`
// this maintains ratios and does not stretch the model
mySCN.rootNode.scale = SCNVector3(scaleValue, scaleValue, scaleValue)

self.mySceneView.scene = mySCN

你可以通过使用边界框的y或z值,根据高度或深度计算比例值。最初的回答中提到了这一点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接