【问题标题】:How do I make a hexagon with 6 triangular SCNNodes?如何用 6 个三角形 SCNNode 制作六边形?
【发布时间】:2020-06-14 12:13:42
【问题描述】:

我正在尝试在不更改任何枢轴点的情况下使用三角形制作六边形网格,但我似乎无法正确定位三角形以制作单个六边形。我正在创建SCNNodesUIBezierPaths 以形成三角形,然后旋转贝塞尔路径。这似乎工作正常,直到我尝试使用参数方程将三角形定位在一个圆圈周围以形成六边形,然后它们最终没有处于正确的位置。你能帮我找出我在哪里做错了吗?

class TrianglePlane: SCNNode {

    var size: CGFloat = 0.1
    var coords: SCNVector3 = SCNVector3Zero
    var innerCoords: Int = 0

    init(coords: SCNVector3, innerCoords: Int, identifier: Int) {
        super.init()

        self.coords = coords
        self.innerCoords = innerCoords
        setup()
    }

    init(identifier: Int) {
        super.init()
//        super.init(identifier: identifier)
        setup()
    }

    required init?(coder aDecoder: NSCoder) { 
        fatalError("init(coder:) has not been implemented") 
    }

    func setup() {
        let myPath = path()
        let geo = SCNShape(path: myPath, extrusionDepth: 0)
        geo.firstMaterial?.diffuse.contents = UIColor.red
        geo.firstMaterial?.blendMode = .multiply
        self.geometry = geo
    }

    func path() -> UIBezierPath {

        let max: CGFloat = self.size
        let min: CGFloat = 0

        let bPath = UIBezierPath()
        bPath.move(to: .zero)
        bPath.addLine(to: CGPoint(x: max / 2, 
                                  y: UIBezierPath.middlePeak(height: max)))
        bPath.addLine(to: CGPoint(x: max, y: min))
        bPath.close()
        return bPath
    }
}

extension TrianglePlane {

    static func generateHexagon() -> [TrianglePlane] {

        var myArr: [TrianglePlane] = []

        let colors = [UIColor.red, UIColor.green, 
                      UIColor.yellow, UIColor.systemTeal, 
                      UIColor.cyan, UIColor.magenta]


        for i in 0 ..< 6  {

            let tri = TrianglePlane(identifier: 0)
            tri.geometry?.firstMaterial?.diffuse.contents = colors[i]
            tri.position = SCNVector3( -0.05, 0, -0.5)

//          Rotate bezier path
            let angleInDegrees = (Float(i) + 1) * 180.0
            print(angleInDegrees)
            let angle = CGFloat(deg2rad(angleInDegrees))
            let geo = tri.geometry as! SCNShape
            let path = geo.path!
            path.rotateAroundCenter(angle: angle)
            geo.path = path

//          Position triangle in hexagon
            let radius = Float(tri.size)/2
            let deg: Float = Float(i) * 60
            let radians = deg2rad(-deg)

            let x1 = tri.position.x + radius * cos(radians)
            let y1 = tri.position.y + radius * sin(radians)
            tri.position.x = x1
            tri.position.y = y1

            myArr.append(tri)
        }

        return myArr
    }

    static func deg2rad(_ number: Float) -> Float {
        return number * Float.pi / 180
    }
}

extension UIBezierPath {

    func rotateAroundCenter(angle: CGFloat) {

        let center = self.bounds.center
        var transform = CGAffineTransform.identity
        transform = transform.translatedBy(x: center.x, y: center.y)
        transform = transform.rotated(by: angle)
        transform = transform.translatedBy(x: -center.x, y: -center.y)
        self.apply(transform)
    }

    static func middlePeak(height: CGFloat) -> CGFloat {
        return sqrt(3.0) / 2 * height
    }
}

extension CGRect {
    var center : CGPoint {
        return CGPoint(x:self.midX, y:self.midY)
    }
}

目前的样子:

应该是什么样子:

【问题讨论】:

  • 我认为你只需要从你的位置角度负 90 度。
  • @JamesP 你指的是哪个位置角度?
  • let deg: Float = Float(i) * 60 ,0 度是 3 点而不是 12 点。
  • 我试过let deg: Float = Float(i) * -30,看起来并没有太大的不同。
  • 对不起,我的意思是let deg: Float = (Float(i) * 60) - 90

标签: ios swift macos scenekit scnnode


【解决方案1】:

获得六边形的最简单方法是使用 6 个缩放的SCNPyramids 带有偏移的枢轴点

import SceneKit

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let sceneView = self.view as! SCNView
        let scene = SCNScene()
        sceneView.scene = scene
        sceneView.allowsCameraControl = true
        sceneView.backgroundColor = NSColor.white

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

        for i in 1...6 {

            let triangleNode = SCNNode(geometry: SCNPyramid(width: 1.15,
                                                           height: 1,
                                                           length: 1))

            // the depth of pyramid is almost zero
            triangleNode.scale = SCNVector3(5, 5, 0.001)

            // move a pivot point from pyramid base to its upper vertex
            triangleNode.simdPivot.columns.3.y = 1     

            triangleNode.geometry?.firstMaterial?.diffuse.contents = NSColor(
                                                      calibratedHue: CGFloat(i)/6,
                                                         saturation: 1.0, 
                                                         brightness: 1.0, 
                                                              alpha: 1.0)

            triangleNode.rotation = SCNVector4(0, 0, 1, 
                                              -CGFloat.pi/3 * CGFloat(i))

            scene.rootNode.addChildNode(triangleNode)
        }
    }
}

【讨论】:

  • 谢谢你,但有没有办法在不改变任何枢轴点的情况下实现这一点?我想保持三角形的位置在它们各自的中心。
【解决方案2】:

目前的代码存在一些问题。首先,正如 cmets 中所指出的,平移的参数方程需要旋转 90 度:

        let deg: Float = (Float(i) * 60) - 90.0

下一个问题是三角形bounding box的中心和三角形的质心不是同一个点。这很重要,因为参数方程计算的是三角形的质心必须位于的位置,而不是其边界框的中心。所以我们需要一种计算质心的方法。这可以通过将以下扩展方法添加到TrianglePlane 来完成:

extension TrianglePlane {
    /// Calculates the centroid of the triangle
    func centroid() -> CGPoint
    {
        let max: CGFloat = self.size
        let min: CGFloat = 0
        let peak = UIBezierPath.middlePeak(height: max)
        let xAvg = (min + max / CGFloat(2.0) + max) / CGFloat(3.0)
        let yAvg = (min + peak + min) / CGFloat(3.0)
        return CGPoint(x: xAvg,  y: yAvg)
    }
}

这允许计算参数方程的正确radius

        let height = Float(UIBezierPath.middlePeak(height: tri.size))
        let centroid = tri.centroid()
        let radius = height - Float(centroid.y)

最后的修正是计算三角形原点到质心的偏移量。这种修正取决于三角形是否被旋转翻转:

        let x1 =  radius * cos(radians)
        let y1 =  radius * sin(radians)
        let dx = Float(-centroid.x)
        let dy = (i % 2 == 0) ? Float(centroid.y) - height  : Float(-centroid.y)
        tri.position.x = x1 + dx
        tri.position.y = y1 + dy

将所有这些放在一起会产生预期的结果。

完整的 ViewController 可以在 gist 中找到

注意将三角形的原点作为质心可以大大简化代码。

【讨论】:

  • @zakdances 您好,您需要对这些问题进行进一步说明吗?
  • 这是一个非常好的解释。干得好?。我花了一些时间尝试不同的方程等,但甚至没有考虑边界框中心与质心。
  • @zakdances 很酷,如果您需要更多信息,请告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多