如何定位 SCNCamera 以使对象“刚好适合”视图?

Posted

技术标签:

【中文标题】如何定位 SCNCamera 以使对象“刚好适合”视图?【英文标题】:How do I position an SCNCamera such that an object "just fits" in view? 【发布时间】:2021-07-18 20:21:39 【问题描述】:

我正在使用 SceneKit 制作 3D 棋盘游戏。棋盘本身是平的,但上面的棋子是 3D 的。我想定位相机,使其以这样的角度俯视电路板:

                camera
               /
              /
             /
            /
           /
board     /
___________

我想定位相机,使整个电路板都在视野范围内,而且不小。我在运行时知道棋盘的尺寸,但在编译时不知道,因为用户可以从许多不同的棋盘中选择来玩游戏。

我试图计算相机至少需要多远才能看到电路板的整个宽度。我画了一个这样的图:

并计算出距离是板宽的一半除以 tan(FOV/2)。

将其转换为代码:

private func setupCamera(boardWidth: CGFloat, maxBoardZ: CGFloat, boardCenterX: CGFloat) 
    cameraNode = SCNNode()
    let camera = SCNCamera()
    cameraNode.camera = camera

    let boardRadius = boardWidth / 2
    let cameraDistance = boardRadius / tan(degreesToRadians(camera.fieldOfView) / 2)
    let cameraHeight: Float = 10

    cameraNode.position = SCNVector3(x: Float(boardCenterX), y: cameraHeight, z: Float(maxBoardZ + cameraDistance))

    cameraNode.eulerAngles.x = -0.5

cameraNode 是我稍后将场景视图的pointOfView 设置为的场景属性。

这在纵向模式下看起来不错(这只是整个手机屏幕的一部分):

如果不切断电路板的一部分,相机就不可能离电路板更近。

但是在横向上,与它所在的 SCNView 相比,该板非常小:

相机本来可以靠得更近的!我本来会期待这样的:

注意白色背景区域 - 那是SCNView

我认为 FOV 可能在横向上发生了变化,但是当我在调试器中检查时,无论方向如何,FOV 始终是 60 度。似乎即使 SCNCamera 有一个 fieldOfView 属性,SCNView 也有它自己的、独立的、取决于其宽度和高度的 FOV,我不知道如何访问它。

如何定位相机,使整个板正好适合SCNView

【问题讨论】:

这只是个人的洋葱,但我认为这是不可能的。 @ElTomato 没关系。欢迎框架挑战答案:) 类似的问题,有 90 张不同大小的地图 - 棘手的东西。靠得更近,使面板更大——反复试验。最终,我创建了一个工具来使用游戏相机设置来构建地图。对我来说,这就是要走的路。对于较大的地图,我添加了基于“那个”地图的最大宽度限制的扫射——效果很好。很痛苦,但是你只有这么多的空间可以使用和使用一个工具,你在第一次构建地图时就做对了。不回答您的问题,但如果您愿意,我可以分享相机课程。 @Voltan 不是 OP,但我会对此非常感兴趣! @Voltan 您的意思是您根据相机位置创建地图,而不是根据地图定位相机?这是一个有趣的想法!我明白了,所以您将相机移近了很多,并且不介意不显示整个地图,因为您有扫射控制。 (我理解正确吗?)我想让我的相机控制器保持简单 - 围绕地图中心绕 y 轴运行,所以我不得不完整地显示整个地图。 【参考方案1】:

好的 - 在这里发布 - 希望没关系。

我只在几张大地图上使用了扫射,但最终还是把它们拿出来了。我希望所有地图都适合 - 它看起来与您的非常相似。所以,是的,我向后工作。我把相机放在我想要的地方,然后调整地图和面板大小,所以我一次只处理一件事。

我有三角形、四边形和六角形的面板。这是一款塔防游戏,因此攻击者可以根据面板类型进行各种移动。

class Camera

    var data = Data.sharedInstance
    var util = Util.sharedInstance
    var gameDefaults = Defaults()
    
    var cameraEye = SCNNode()
    var cameraFocus = SCNNode()
        
    var centerX: Int = 100
    var strafeDelta: Float = 0.8
    var zoomLevel: Int = 35
    var zoomLevelMax: Int = 35              // Max number of zoom levels
    
    //********************************************************************
    init()
    
        cameraEye.name = "Camera Eye"
        cameraFocus.name = "Camera Focus"
        
        cameraFocus.isHidden = true
        cameraFocus.position  =  SCNVector3(x: 0, y: 0, z: 0)
        
        cameraEye.camera = SCNCamera()
        cameraEye.constraints = []
        cameraEye.position = SCNVector3(x: 0, y: 15, z: 0.1)
        
        let vConstraint = SCNLookAtConstraint(target: cameraFocus)
        vConstraint.isGimbalLockEnabled = true
        cameraEye.constraints = [vConstraint]
    
    //********************************************************************
    func reset()
    
        centerX = 100
        cameraFocus.position  =  SCNVector3(x: 0, y: 0, z: 0)
        cameraEye.constraints = []
        cameraEye.position = SCNVector3(x: 0, y: 32, z: 0.1)
        cameraFocus.position = SCNVector3Make(0, 0, 0)
        
        let vConstraint = SCNLookAtConstraint(target: cameraFocus)
        vConstraint.isGimbalLockEnabled = true
        cameraEye.constraints = [vConstraint]
    
    //********************************************************************
    func strafeRight()
    
        if(centerX + 1 < 112)
        
            centerX += 1
            cameraEye.position.x += strafeDelta
            cameraFocus.position.x += strafeDelta
        
    
    //********************************************************************
    func strafeLeft()
    
        if(centerX - 1 > 90)
        
            centerX -= 1
            cameraEye.position.x -= strafeDelta
            cameraFocus.position.x -= strafeDelta
        
    
    //********************************************************************

我是这样使用 GKGraph 的:

var graphNodes: [GKPanelNode] = [] // All active graph nodes with connections
var myGraph = GKGraph()   // declaring the Graph

然后是您的典型面板负载,可能与您的相似

func getQuadPanelNode(vPanelType: panelTypes) -> SCNNode
    
        let plane = SCNBox(width: 1.4, height: 0.001, length: 1.4, chamferRadius: 0)
        
        plane.materials = []
        plane.materials = setQuadPanelTextures(vPanelType: vPanelType)
        plane.firstMaterial?.isDoubleSided = false
        return SCNNode(geometry: plane)
    

然后从我创建的地图中加载面板。

func loadPanels()
    
        removePanelNodes()
        for vPanel in mapsDetail.getDetail(vMap: data.mapSelected)
        
            let vPanel = Panel.init(vName: "Panel:" + vPanel.name, vPanelType: vPanel.type, vPosition: vPanel.pos, vRotation: vPanel.up)
            gridPanels[vPanel.panelName] = vPanel
            
            if(vPanel.type == .entry)  entryPanelName = vPanel.panelName 
            if(vPanel.type == .exit)   exitPanelName  = vPanel.panelName 
        
    

【讨论】:

以上是关于如何定位 SCNCamera 以使对象“刚好适合”视图?的主要内容,如果未能解决你的问题,请参考以下文章

在 SCNCamera 的当前方向前面放置一个 SceneKit 对象

SCNScene 中 SCNCamera 的默认位置

使用属性键路径为 SCNCamera.fStop 设置动画不起作用,仅在隐式 SCNTransaction 内

有没有办法移动 Scene Kit 相机的主点?

我如何重载 new/stl 以使未知对象更快?

如何请求用户开启定位服务