简体   繁体   English

如何制作具有 6 个三角形 SCNNode 的六边形?

[英]How do I make a hexagon with 6 triangular SCNNodes?

I'm trying to make a hexagon grid with triangles without altering any pivot points, but I can't seem to position the triangles correctly to make single hexagon.我试图在不改变任何枢轴点的情况下制作带有三角形的六边形网格,但我似乎无法正确定位三角形以制作单个六边形。 I'm creating SCNNodes with UIBezierPaths to form triangles and then rotating the bezier paths.我正在使用UIBezierPaths创建SCNNodes以形成三角形,然后旋转UIBezierPaths路径。 This seems to work fine UNTIL I try to use a parametric equation to position the triangles around a circle to form the hexagon, then they don't end up in the correct position.这似乎工作正常,直到我尝试使用参数方程将三角形定位在圆形周围以形成六边形,然后它们最终不会处于正确的位置。 Can you help me spot where I'm doing wrong here?你能帮我找出我在这里做错的地方吗?

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)
    }
}

What it currently looks like:它目前的样子:

在此处输入图片说明

What it SHOULD look like:它应该是什么样子:

在此处输入图片说明

The simplest way to get a hexagon is to use 6 scaled SCNPyramids with offsetted pivot points :获得六边形的最简单方法是使用 6 SCNPyramids带有偏移枢轴点的缩放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)
        }
    }
}

在此处输入图片说明

There are a few problems with the code as it stands.代码目前存在一些问题。 Firstly, as pointed out in the comments, the parametric equation for the translations needs to be rotated by 90 degrees:首先,正如评论中所指出的,平移的参数方程需要旋转 90 度:

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

The next issue is that the centre of the bounding box of the triangle and the centroid of the triangle are not the same point.下一个问题是三角形的边界框的中心和三角形的质心不是同一个点。 This is important because the parametric equation calculates where the centroids of the triangles must be located, not the centres of their bounding boxes.这很重要,因为参数方程计算三角形的质心必须位于何处,而不是其边界框的中心。 So we're going to need a way to calculate the centroid.所以我们需要一种计算质心的方法。 This can be done by adding the following extension method to TrianglePlane :这可以通过向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)
    }
}

This allows the correct radius for the parametric equation to be calculated:这允许计算参数方程的正确radius

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

The final correction is to calculate the offset between the origin of the triangle and the centroid.最后的修正是计算三角形原点和质心之间的偏移量。 This correction depends on whether the triangle has been flipped by the rotation or not:这个修正取决于三角形是否被旋转翻转了:

        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

Putting all this together gives the desired result.将所有这些放在一起给出了预期的结果。

在此处输入图片说明

Full working ViewController can be found int this gist完整工作的 ViewController 可以在这个要点中找到

Note the code can be greatly simplified by making the origin of the triangle be the centroid.请注意,通过将三角形的原点作为质心,可以大大简化代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM