我有一个用SWIFT和SceneKit构建的3D地球仪,为了表示用户的当前位置,我使用了一个圆环(甜甜圈形状的对象).用户的位置可以在世界上的任何地方,简单地将 node 添加到Globe并不能使其与Globe对齐(与Globe对齐).我必须修改 node EulerAngles,以使其与地球平坦.我的功能不能正确地将形状与地球对齐,因为给定了任何唯一的位置.我想要的功能,以定位环平的基础上的任何一组纬度和经度.

    let dotNode = SCNNode(geometry: lowerCircle)
 
    dotNode.eulerAngles = getRotation(lat: viewModel.currentLocation?.lat ?? 0.0, long: viewModel.currentLocation?.long ?? 0.0)

    func getRotation(lat: Double, long: Double) -> SCNVector3 {
        let latRad = lat * .pi / 180.0
        let lonRad = long * .pi / 180.0

        let rotationX = Float(-latRad)
        let rotationY = Float(lonRad)
        let rotationZ = Float(lonRad)

        return SCNVector3Make(rotationX, rotationY, rotationZ)
        
        //example
        //slightly correct rotation for Saudi Arabia
        //SCNVector3Make(.pi / 3.75, 0, -.pi / 4)
    }

推荐答案

在3D图形或建模环境中(就像在Swift中使用的SceneKit框架中的SCNTorus一样),torus是一个类似于甜甜圈或环的几何形状.

Torus

在您的例子中,圆环被用来表示地球上的特定位置,很可能是用户的当前位置.

由于球体是球体,简单地将圆环放置在基于经度和纬度的点上是不够的.圆环还需要定向,以使其与地球表面齐平.这需要调整它的自转以与地球在该特定点的曲率适当地对齐.

圆环是地球 node 的子 node .

圆环是地球 node 的子 node ,应用于该圆环的任何旋转或定位都相对于地球 node .这简化了在特定地理位置将圆环与地球表面对齐的过程.无需考虑整个场景的坐标系,只需相对于地球 node 调整圆环即可.

这将是:

func getTorusOrientation(lat: Double, long: Double) -> SCNVector4 {
    let latRad = lat * .pi / 180.0
    let lonRad = long * .pi / 180.0

    // Calculate position vector
    let x = cos(latRad) * cos(lonRad)
    let y = cos(latRad) * sin(lonRad)
    let z = sin(latRad)

    let positionVector = SCNVector3(x, y, z)

    // Updated up vector (assuming Y-axis is 'up' in the coordinate system)
    let upVector = SCNVector3(0, 1, 0)

    // Calculate cross-product
    let crossProduct = positionVector.cross(upVector)

    // Calculate angle for rotation
    let angle = acos(positionVector.dot(upVector))

    return SCNVector4(crossProduct.x, crossProduct.y, crossProduct.z, angle)
}

extension SCNVector3 {
    func cross(_ vector: SCNVector3) -> SCNVector3 {
        return SCNVector3(
            self.y * vector.z - self.z * vector.y,
            self.z * vector.x - self.x * vector.z,
            self.x * vector.y - self.y * vector.x
        )
    }

    func dot(_ vector: SCNVector3) -> Float {
        return (self.x * vector.x + self.y * vector.y + self.z * vector.z)
    }
}

// Usage
dotNode.orientation = getTorusOrientation(lat: viewModel.currentLocation?.lat ?? 0.0, 
                                          long: viewModel.currentLocation?.long ?? 0.0)

positionVector是使用纬度(lat)和经度(long)值计算的.该向量从地球中心(假定为该坐标系中的原点)指向地球表面上应放置圆环的位置.

upVector设置为(0, 1, 0),这是在许多3D坐标系中定义向上方向的标准方式.

positionVectorupVectorcrossProduct是计算出来的:它给出了我们需要绕其旋转圆环的轴.该轴垂直于圆环的位置向量和初始上方向向量,这正是正确方向所需的.

旋转的angle是使用positionVectorupVector之间的点积计算的.该Angular 是圆环绕crossProduct轴旋转以与指定地理位置处的地球曲面正确对齐所需的量.

然后使用从crossProduct轴创建的SCNVector4angle来设置圆环的方向.这确保了圆环不仅位于正确的经纬度,而且方向正确,使其看起来与地球表面平齐.


请注意,来自SceneKit的SCNTorus可以重复使用上面定义的getTorusOrientation().

// Create a torus
let torus = SCNTorus(ringRadius: 1.0, pipeRadius: 0.1)
let torusNode = SCNNode(geometry: torus)

// Set the torus orientation (using the previously discussed getTorusOrientation function)
torusNode.orientation = getTorusOrientation(lat: viewModel.currentLocation?.lat ?? 0.0, 
                                            long: viewModel.currentLocation?.long ?? 0.0)

// Add the torus node to the earth node
earthNode.addChildNode(torusNode)

您将创建一个具有特定半径值的SCNTorus对象,然后将其包装在SCNNode(即torusNode)中.使用getTorusOrientation函数设置该 node 的方向,使其在球体上正确对齐.最后,将圆环 node 作为子 node 添加到地球 node ,以维护正确定位和定向所需的层次关系.


也许我可以修改EulerAngles以应用正确的旋转.如果这是一种可行的方法,你知道如何做到这一点吗?

Modifying Euler angles directly can be a viable approach, but trickier because of issues like gimbal lock. See for instance "Understanding Gimbal Lock and how to prevent it".
It can be more intuitive in some cases, especially when dealing with geographic coordinates. But it may not handle all rotation scenarios as well as quaternions.

关键是首先旋转圆环 node 的纬度(绕X轴),然后旋转经度(绕Y轴):

func getEulerAngles(lat: Double, long: Double) -> SCNVector3 {
    // Convert lat and long to radians
    let latRad = lat * .pi / 180.0
    let longRad = long * .pi / 180.0

    // For latitude: Rotate around the X-axis
    let rotationX = -latRad // Negative because SceneKit uses a right-handed coordinate system

    // For longitude: Rotate around the Y-axis
    let rotationY = longRad

    // No rotation around the Z-axis
    let rotationZ = 0.0

    return SCNVector3(rotationX, rotationY, rotationZ)
}

// Usage
dotNode.eulerAngles = getEulerAngles(lat: viewModel.currentLocation?.lat ?? 0.0, 
                                     long: viewModel.currentLocation?.long ?? 0.0)

确保您的地球仪在SceneKit中方向正确.在此设置中,地球北极应沿正Y轴对齐.这应该根据给定的纬度和经度正确地定位圆环.


Your solution using the Euler angles is basically correct.
If you add a minus 1.57 rads like let rotationX = -latRad - 1.57 then the solution works perfectly, and the torus is flush anywhere on the globe. I am not sure why you have to subtract 90 degrees, but it works.

需要从绕X轴的旋转中减go 1.57弧度(约为90度),可以通过了解圆环的初始方向以及它需要如何相对于地球表面定位来解释.

默认情况下,圆环体的方向可能会使其"孔"沿Y轴朝上.这是三维建模中环形或圆环体等对象的常见默认方向.

设置纬度(rotationX)的旋转时,实际上是向前或向后倾斜圆环以匹配纬度.但是,如果圆环的孔最初朝上,则需要额外旋转90度(或π/2弧度),以使圆环位于纬度圆的平面内.

减go 1.57(或π/2)弧度会旋转圆环,使其与地球上的纬度线正确对齐.如果没有这种调整,环面将直立在赤道,而不是平躺在地球表面.

更新后的功能将是:

func getEulerAngles(lat: Double, long: Double) -> SCNVector3 {
    let latRad = lat * .pi / 180.0
    let longRad = long * .pi / 180.0

    let rotationX = -latRad - 1.57 // Adjust for the initial orientation of the torus
    let rotationY = longRad
    let rotationZ = 0.0

    return SCNVector3(rotationX, rotationY, rotationZ)
}

// Usage
dotNode.eulerAngles = getEulerAngles(lat: viewModel.currentLocation?.lat ?? 0.0, 
                                     long: viewModel.currentLocation?.long ?? 0.0)

Ios相关问答推荐

在Android和iOS上从后台恢复应用程序后,inappwebview上出现白屏?

通过拖动更改视图位置的SwiftUI会引发UI错误

删除领域中的cartItem时出现问题

SwiftUI图表-默认情况下,X轴数据点的间距不相等

BezierPath 中绘制图像形状轮廓的算法(Canny 边缘检测器)

如何在 Flutter 应用程序中测试来自 App/Play Store 的移动深层链接?

当目标位于菜单标签内时,matchedGeometryEffect 的行为很奇怪

NSError 错误领域: "CBATTErrorDomain" 代码: 128 0x0000000282b111a0

iOS设备网页自动化中,在向字段发送文本后未启用按钮

不使用故事板创建 iMessage 应用程序

如何更改 SwiftUI 弹出视图的大小?

Flutter iOS 构建失败:DVTCoreDeviceEnabledState_Disabled

由于缺少 libarclite_iphonesimulator.a 文件,将 XCode 升级到 14.3 后无法在模拟器上运行 Flutter 应用程序

xcode-select:找不到clang

如何将单个按钮传递到自定义 ConfirmationDialog

SwiftUI - 使用切换switch 切换键盘类型(默认/数字)?

按钮在 SwiftUI 中没有采用给定的高度和宽度

Apple Push Service 证书不受信任

在 iOS 中获取设备位置(仅限国家/地区)

在 iOS 上存储身份验证令牌 - NSUserDefaults 与 keys 串?