【问题标题】:SCNShape doesn't draw a shape for NSBezierPathSCNShape 不为 NSBezierPath 绘制形状
【发布时间】:2017-04-22 10:44:41
【问题描述】:

我经历了一些NSBezierPaths 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)

为了验证创建SCNShape 的一般过程是否正确,我还绘制了一个蓝色形状,它的区别仅在于具有不同的点。蓝色形状被绘制,绿色形状没有。

您可以找到包含完整示例 here 的游乐场。在示例中,您应该能够在助手编辑器中看到绿色和蓝色的形状。但只绘制了蓝色形状。

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

【问题讨论】:

  • 如果可以将 Illustrator 或其他矢量程序中的形状复制并粘贴到 SceneKit 的场景编辑器中,该有多好?然后对其进行缩放...等等。或者导入 SVG,或其他某种单独的矢量形状。

标签: ios swift macos scenekit


【解决方案1】:

简短的故事:你的路径比它需要的点多得多,导致你遇到意想不到的、难以找到的几何问题。

注意documentation中的这一点:

拉伸自相交路径的结果是未定义的。

事实证明,在前 8 个左右点的某个地方,您的“曲线”以错误的方式转动了足够多的路径,使得关闭路径的线(在路径 0,0 的第一个点和最后一个点之间)点32.366829, 29.713470) 与路径的其余部分相交。这是通过从操场渲染中排除除前几个点和最后一个点之外的所有点来使其可见的尝试(请参见左下角的小锯齿形):

至少在某些 SceneKit 版本/渲染器上,当它试图从自相交路径中创建网格时,它只是放弃并且什么都不做。

但是,您确实不需要那么多点来使您的路径看起来不错。如果您使用 1x、1/5x 和 1/10x 的点数,则为:

如果您总体排除了足够多的点,和/或在开始时跳过了一些使您的曲线曲折的点,SceneKit 会很好地呈现形状:


诊断问题的一些技巧:

当处理大量这样的坐标数据时,我喜欢使用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],
    // ...
]

这让我得到了一个数组(并且更少地反复输入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])
        }
    }
}

有了这个,我不仅可以从整个点数组轻松创建路径,还可以...

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

    let path5 = NSBezierPath(linesBetween: points, stride: 5)
    let path10 = NSBezierPath(linesBetween: points, stride: 10)
    

    (这对于更快地生成 Playground 预览也很方便。)

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

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

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

let path = NSBezierPath(linesBetween: Array(points.suffix(from: 10)), stride: 5)

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

【讨论】:

  • 哇。哇!这是一流的文档。我代表所有习惯于用笔、鼠标或平板电脑绘图的受挫的设计导向、触觉插画家,感谢你们!
  • 路径的问题是,它是由我的应用程序的用户创建的,所以我事先不知道路径将包含多少个和哪个点。谢谢你的好答案!! :)
  • 为了收集此示例的数据,我们要求用户在空中画一个圆圈。显然,结局并不是一个完美的循环。理想情况下,我们会将其识别为一个圆圈,然后更改数据以绘制一个完美的圆圈。但这是一个不同的问题......
  • 是的,您绝对不能指望能够将用户草图提供给SCNShape。正如您所指出的,将“圆形”识别为实际的圆形是一个完全独立的问题……尽管您可以利用数十年发表的研究。
【解决方案2】:

这与 Rikster 的答案完全不同,但还有另一种方法可以防止此类问题。这是一种商业方式,并且可能有类似的免费软件应用程序,但这是我习惯使用的一个,它做得很好。

我说的“这个”是什么?

通过名为 PaintCode 的应用将绘图转换为代码。这将允许您查看您的路径并确保它们没有 Rickster 指出的冲突是您的问题。

在这里查看:https://www.paintcodeapp.com/

其他选项在此处的答案中列出:How to import/parse SVG into UIBezierpaths, NSBezierpaths, CGPaths?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-30
    • 2018-04-02
    • 2018-09-24
    • 2015-04-23
    • 2018-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多