SCNShape不为NSBezierPath绘制形状

Jör*_*hof 2 macos ios scenekit swift

我经历了一段NSBezierPath小号 SCNShape似乎无法绘制形状.

仅使用创建路径line(to:).

//...set up scene...

//Create path (working)
let path = NSBezierPath()
path.move(to: CGPoint.zero)
path.line(to: NSMakePoint(0.000000, 0.000000))
path.line(to: NSMakePoint(0.011681, 0.029526))
// more points ...
path.close()

// Make a 3D shape (not working)
let shape = SCNShape(path: path, extrusionDepth: 10)
shape.firstMaterial?.diffuse.contents = NSColor.green
let node = SCNNode(geometry: shape)
root.addChildNode(node)
Run Code Online (Sandbox Code Playgroud)

为了验证创建a的一般过程SCNShape是否正确,我还画了一个蓝色的形状,只有不同的点才有所不同.蓝色形状被绘制,绿色形状不被绘制.

你可以在这里找到一个包含完整示例的游乐场.在示例中,您应该能够在助理编辑器中看到绿色和蓝色形状.但只有蓝色的形状被绘制出来.

你知道为什么没有显示绿色形状吗?

ric*_*ter 6

简短的故事:你的路径有更多的点,而不是它需要,导致你意想不到的,很难找到几何问题.

请注意文档中的这一位:

挤出自相交路径的结果是不确定的.

事实证明,在前8个点左右的某个地方,你的"曲线"使得转弯的方式足够错误,因为关闭路径的线(在路径中的第一个点0,0和最后一个点之间32.366829, 29.713470)与其余部分相交路径.这是尝试通过从游乐场渲染中排除除了前几个点和最后一个点之外的所有点来看到它(见左下角那个小小的锯齿形):

为了正义而移动zig

至少在一些SceneKit版本/渲染器上,当它试图从一个自相交的路径中制作一个网格时,它只是放弃并且什么都不做.

但是,你真的不需要那么多点来让你的路径看起来很好.在这里,如果您使用1x,1/5x和1/10x多个点:

原始路径 路径w /每隔5点从原始 路径w /每10点从原始

如果你总体上排除了足够的点数,和/或在开头跳过几个使你的曲线zag它应该zig的东西,SceneKit渲染形状就好了:

排除前几点,占总数的1/5


诊断问题的一些提示:

当处理大量像这样的坐标数据时,我喜欢使用,ExpressibleByArrayLiteral所以我可以很容易地构建一个点/向量/等的数组:

extension CGPoint: ExpressibleByArrayLiteral {
    public init(arrayLiteral elements: CGFloat...) {
        precondition(elements.count == 2)
        self.init(x: elements.first!, y: elements.last!)
    }
}

var points: [CGPoint] = [
    [0.000000, 0.000000],
    [0.011681, 0.029526],
    // ...
]
Run Code Online (Sandbox Code Playgroud)

这让我得到了一个数组(而不是一次又一次地输入类似的东西NSPointMake),所以我可以对数据进行切片和切块以找出它的错误.(例如,我早期的一个理论是可能存在关于负坐标的东西,所以我做了一些mapmin()找到最负的X和Y值,然后再map制作一个数组,其中所有点都被一个常数偏移量.)

现在,为了使用点数组来创建路径,我对NSBezierPath以下内容进行了扩展:

extension NSBezierPath {
    convenience init(linesBetween points: [CGPoint], stride: Int = 1)  {
        precondition(points.count > 1)
        self.init()
        move(to: points.first! )
        for i in Swift.stride(from: 1, to: points.count, by: stride) {
            line(to: points[i])
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我可以轻松地从不仅仅是整个点阵列创建路径,而且......

  • 跳过原始数组部分的路径(带stride参数)

    let path5 = NSBezierPath(linesBetween: points, stride: 5)
    let path10 = NSBezierPath(linesBetween: points, stride: 10)
    
    Run Code Online (Sandbox Code Playgroud)

    (这对于更快地生成游乐场预览也很方便.)

  • 使用原始数组的一些块或切片的路径

    let zigzag = NSBezierPath(linesBetween: Array(points.prefix(to:10)) + [points.last!])
    let lopOffBothEnds = NSBezierPath(linesBetween: Array(points[1 ..< points.count < 1]))
    
    Run Code Online (Sandbox Code Playgroud)

或两者......获胜的条目(在上面的屏幕截图中)是:

let path = NSBezierPath(linesBetween: Array(points.suffix(from: 10)), stride: 5)
Run Code Online (Sandbox Code Playgroud)

你可以通过路径中的更多点来获得(略微)更好的渲染,但更好的方法是从曲线而不是线条中创建路径.为了额外的功劳,尝试扩展NSBezierPath(linesBetween:)上面的初始化程序,通过将每个n点作为路径的一部分来添加曲线,同时使用一些中间点作为控制句柄.(这不是通用的自动跟踪算法,但对于这样的情况可能已经足够了.)