You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何利用SceneKit中的坐标绘制2D UIView室内平面图?

嘿,这个需求我之前做室内扫描类APP时刚好碰过,核心就是把3D空间的X/Z坐标映射到2D UI坐标系,再用Core Graphics画出路径就行。给你一步步拆解实现方案:

步骤1:预处理3D坐标,转换为UI可用的2D坐标

首先得解决坐标系差异的问题:SceneKit的世界坐标和UIKit的坐标系规则完全不同——SceneKit里X是水平向右、Z是垂直向前(远离相机)、Y是向上;而UIKit的原点在左上角,X向右、Y向下。

所以第一步要做这几件事:

  • 忽略所有球体节点的Y轴坐标,只保留X和Z值
  • 把坐标平移到UI坐标系的正区域(避免出现负数坐标,导致绘制出界)
  • 设置缩放比例:真实世界的1米对应UI上的多少点(比如100pt/米,你可以根据UI布局需求调整)

Swift代码示例:

// 假设你的球体节点数组是sphereNodes: [SCNNode]
let worldPoints = sphereNodes.map { node in
    CGPoint(x: CGFloat(node.position.x), y: CGFloat(node.position.z))
}

// 计算所有点的最小X和最小Z,用于平移坐标
let minX = worldPoints.min { $0.x < $1.x }?.x ?? 0
let minZ = worldPoints.min { $0.y < $1.y }?.y ?? 0

// 平移后确保所有坐标非负
let translatedPoints = worldPoints.map { point in
    CGPoint(x: point.x - minX, y: point.y - minZ)
}

// 设置缩放比例,比如1米对应100UI点
let scale: CGFloat = 100.0
let scaledPoints = translatedPoints.map { point in
    CGPoint(x: point.x * scale, y: point.y * scale)
}
步骤2:对坐标点排序(关键!)

如果你的节点是随机添加的,直接连线会变成一团乱麻。所以必须把这些房间角落点按顺时针或逆时针顺序排列,这样才能画出正确的房间轮廓。常用的排序方法是:

  1. 计算所有点的中心点
  2. 算出每个点相对于中心点的角度
  3. 按角度排序(逆时针或顺时针都可以)

Swift代码示例:

// 计算所有点的中心点
let centerX = scaledPoints.reduce(0) { $0 + $1.x } / CGFloat(scaledPoints.count)
let centerY = scaledPoints.reduce(0) { $0 + $1.y } / CGFloat(scaledPoints.count)
let center = CGPoint(x: centerX, y: centerY)

// 按逆时针角度排序,确保连线顺序正确
let sortedPoints = scaledPoints.sorted { p1, p2 in
    let angle1 = atan2(p1.y - center.y, p1.x - center.x)
    let angle2 = atan2(p2.y - center.y, p2.x - center.x)
    return angle1 < angle2
}

如果你的节点是用户绕房间走一圈依次添加的(已经按顺序排列),这一步可以直接跳过。

步骤3:在UIView中绘制2D平面图

创建一个自定义UIView,重写draw(_:)方法,用UIBezierPath绘制闭合路径,就能得到房间的2D平面图了:

class FloorPlanView: UIView {
    // 接收处理好的排序后坐标点
    var floorPoints: [CGPoint] = [] {
        didSet {
            setNeedsDisplay() // 点更新后触发重绘
        }
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard !floorPoints.isEmpty else { return }
        
        let path = UIBezierPath()
        // 移动到第一个点
        path.move(to: floorPoints[0])
        // 依次连接所有点
        for point in floorPoints.dropFirst() {
            path.addLine(to: point)
        }
        // 闭合路径,形成完整的房间轮廓
        path.close()
        
        // 设置绘制样式:描边颜色、宽度,填充透明度
        UIColor.systemBlue.setStroke()
        path.lineWidth = 2.0
        UIColor.systemBlue.withAlphaComponent(0.2).setFill()
        
        // 执行绘制
        path.stroke()
        path.fill()
    }
}

之后在你的ViewController里,把处理好的sortedPoints赋值给这个自定义View就行:

// 假设你已经通过Storyboard或代码创建了floorPlanView实例
floorPlanView.floorPoints = sortedPoints
额外优化建议
  • 如果采集的点里有多余的杂点,可以用凸包算法(比如Graham扫描法)自动提取房间的外轮廓,过滤无效点
  • 可以给每个角落点添加坐标标签,显示真实的距离(用缩放比例反推即可)
  • 支持手势缩放、拖动平面图,方便用户查看细节

内容的提问来源于stack exchange,提问作者Lavrin Pristazh

火山引擎 最新活动