Swift 如何弯曲圆锯片&x201C;行”;?

Swift 如何弯曲圆锯片&x201C;行”;?,swift,scenekit,Swift,Scenekit,我正在尝试使用scenekit创建一个照明螺栓,我正在遵循这一点。到目前为止,我已经在我的场景中使用带有拉伸的UIBezierPath获得了一条垂直线,以使其成为3d,但我不确定如何在中点弯曲“线”,如链接中所述 func createBolt() { let path = UIBezierPath() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: 1)) path

我正在尝试使用scenekit创建一个照明螺栓,我正在遵循这一点。到目前为止,我已经在我的场景中使用带有拉伸的UIBezierPath获得了一条垂直线,以使其成为3d,但我不确定如何在中点弯曲“线”,如链接中所述

func createBolt() {
     let path = UIBezierPath()
     path.move(to: CGPoint(x: 0, y: 0))
     path.addLine(to: CGPoint(x: 0, y: 1))
     path.close()


    let shape = SCNShape(path: path, extrusionDepth 0.2)
    let color = UIColor.red
    shape.firstMaterial?.diffuse.contents = color


    let boltNode = SCNNode(geometry: shape)
    boltNode.position.z = 0
    sceneView.scene.rootNode.addChildNode(boltNode)
}

算法非常简单:
A
B
的1段列表开始,然后在每一代中,通过移动其范数上随机偏移的中点,将每个段拆分为2段

struct段{
让我们开始:CGPoint
让我们结束:CGPoint
}
///计算二维向量的范数
func范数(v:CGPoint)->CGPoint{
设d=max(sqrt(v.x*v.x+v.y*v.y),0.0001)
返回点(x:v.x/d,y:v.y/-d)
}
///在两个线段上拆分线段,中间点在范数上偏移'offset'
func分割(uu段:段,按偏移量:CGFloat)->[段]{
变量中点=(segment.start+segment.end)/2
中点=标准(段.结束-段.开始)*偏移+中点
返回[
分段(起点:分段。起点,终点:中点),
线段(起点:中点,终点:线段。终点)
]
}
///生成从“开始”到“结束”的螺栓状线,最大开始频率为“maxOffset”`
///和“生成”拆分循环
func generate(从开始:CGPoint,到结束:CGPoint,带偏移量maxOffset:CGFloat,世代:Int=6)->UIBezierPath{
变量段=[段(开始:开始,结束:结束)]
var offset=maxOffset
对于uu0..<代{
segments=segments.flatMap{split($0,by:CGFloat.random(in:-offset…offset))}
偏移量/=2
}
let path=UIBezierPath()
路径。移动(到:开始)
segments.forEach{path.addLine(to:$0.end)}
返回路径
}
//马克:举个例子
让起点=CGPoint(x:10,y:10)
让端点=CGPoint(x:90,y:90)
let path=generate(from:start,to:end,withOffset:30,generations:5)
//马克:助手
func+(左:CGPoint,右:CGPoint)->CGPoint{
返回点(x:lhs.x+rhs.x,y:lhs.y+rhs.y)
}
func-(左:CGPoint,右:CGPoint)->CGPoint{
返回点(x:lhs.x-rhs.x,y:lhs.y-rhs.y)
}
func/(左:CGPoint,右:CGFloat)->CGPoint{
返回点(x:lhs.x/rhs,y:lhs.y/rhs)
}
func*(左:CGPoint,右:CGFloat)->CGPoint{
返回点(x:lhs.x*rhs,y:lhs.y*rhs)
}

SceneKit制造避雷针 这里提供了另一种方法,如何在SceneKit中创建随机的、完整的3D闪电(谢谢Harry!)

使用默认的游戏模板(在3D空间中显示飞机的模板)在Xcode中创建一个新的SceneKit项目(用于iOS),删除飞机并创建一个黑色背景的空场景。还可以全局定义您的
sceneView
(以便能够从其他类访问它)

将以下类和扩展名添加到新的Swift文件(
import SceneKit
):

班级 类LightningStrike

class LightningStrike : Geometry {
    
    var bolt:[Lightning] = []
    
    var start = SCNVector3() // stores start position of the Bolt
    var end = SCNVector3() // stores end position of the Bolt
    
    static var delayTime = 0.0
    
    override init() {
        start = SCNVector3(0.0, +5.0, 0.0) // default, to be changed
        end   = SCNVector3(0.0, -5.0, 0.0) // default, to be changed
        
        print("Lightning Strike initialized")
    }
    
    private func fadeOutBolt() {
        for b in bolt {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 2.0
            b.face.geometry?.firstMaterial?.transparency = 0.0
            SCNTransaction.commit()
        }
    }
    
    func strike() {
        for b in bolt { b.face.removeFromParentNode() }
        bolt.removeAll()
        
        // Create Main Bolt
        bolt.append(Lightning())
        bolt[0].createBolt(start,end)
        sceneView.scene?.rootNode.addChildNode(bolt[0].face)
        
        // Create child Bolts
        for _ in 0 ..< 15 { // number of child bolts
            // let parent = Int.random(in: 0 ..< bolt.count)  // random parent bolt, an other method
            let parent : Int = 0
            
            let start = bolt[parent].centerLine[10 + Int.random(in: 0 ..< 15)] // random node to start from off of parent, pay attention to: numSegments - changing numbers here can cause out of index crash
            let length:SCNVector3 = bolt[parent].end.minus(start) // length from our start to end of parent
            var end = SCNVector3()
            end.x = start.x + length.x / 1.5 + Float.random(in: 0 ... abs(length.x) / 3) // adjust by playing with this numbers
            end.y = start.y + length.y / 1.5 + Float.random(in: 0 ... abs(length.y) / 3) // adjust by playing with this numbers
            end.z = start.z + length.z / 1.5 + Float.random(in: 0 ... abs(length.z) / 3) // adjust by playing with this numbers

            bolt.append(Lightning())
            let index = bolt.count-1
            bolt[index].width = bolt[parent].width * 0.2
            bolt[index].deviation = bolt[parent].deviation * 0.3
            bolt[index].createBolt(start,end)
            sceneView.scene?.rootNode.addChildNode(bolt[0].face)
        }
        
        // Reset delay time and schedule fadeOut
        LightningStrike.delayTime = 0.0 // reset delay time
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { self.fadeOutBolt() }
        
        // Here you can add a Sound Effect
    }
    
    deinit {
        for b in bolt { b.face.removeFromParentNode() }
        bolt.removeAll()
        print("Lightning Strike deinitialized")
    }
    
}
class Lightning : Geometry {
    let UNASSIGNED:Float = 999
    var start = SCNVector3()
    var end = SCNVector3()
    var numSegments = Int() // use => 3,5,9,17,33,65
    var width = Float()
    var deviation = Float()
    var vertices:[SCNVector3] = []
    var normals:[SCNVector3] = []
    var indices:[Int32] = []
    var centerLine:[SCNVector3] = []
    var face:SCNNode! = nil
    
    override init() {
        
        numSegments = 33 // 17
        width = 0.1
        deviation = 1.5
        
        centerLine = Array(repeating: SCNVector3(), count: numSegments)
        
        // indexed indices never change
        var j:Int = 0
        for i  in 0 ..< numSegments-1 {
            j = i * 3
            indices.append(Int32(j + 0))  // 2 triangles on side #1
            indices.append(Int32(j + 2))
            indices.append(Int32(j + 3))
            indices.append(Int32(j + 2))
            indices.append(Int32(j + 5))
            indices.append(Int32(j + 3))
            
            indices.append(Int32(j + 2))  // side #2
            indices.append(Int32(j + 1))
            indices.append(Int32(j + 5))
            indices.append(Int32(j + 1))
            indices.append(Int32(j + 4))
            indices.append(Int32(j + 5))
            
            indices.append(Int32(j + 1))  // side #3
            indices.append(Int32(j + 0))
            indices.append(Int32(j + 4))
            indices.append(Int32(j + 0))
            indices.append(Int32(j + 3))
            indices.append(Int32(j + 4))
        }
    }
    
    func createNode() -> SCNGeometry {
        for i  in 0 ..< numSegments { centerLine[i].x = UNASSIGNED }
        centerLine[0] = start
        centerLine[numSegments-1] = end
        
        var hop:Int = max(numSegments / 2,1)
        var currentDeviation = deviation
        while true {
            for i in stride(from:0, to: numSegments, by:hop) {
                if centerLine[i].x != UNASSIGNED { continue }
                let p1 = centerLine[i-hop]
                let p2 = centerLine[i+hop]
                centerLine[i] = SCNVector3(
                    (p1.x + p2.x)/2 + Float.random(in: -currentDeviation ... currentDeviation),
                    (p1.y + p2.y)/2 + Float.random(in: -currentDeviation ... currentDeviation),
                    (p1.z + p2.z)/2 + Float.random(in: -currentDeviation ... currentDeviation))
            }
            
            if hop == 1 { break }
            hop /= 2
            currentDeviation *= 0.6
        }
        
        vertices.removeAll()
        normals.removeAll()
        
        // triangle of vertices at each centerLine node on XZ plane
        let ss:[Float] = [ sin(0), sin(Float.pi * 2/3), sin(Float.pi * 4/3)]
        let cc:[Float] = [ cos(0), cos(Float.pi * 2/3), cos(Float.pi * 4/3)]
        
        var w = width
        for i  in 0 ..< numSegments {
            for j in 0 ..< 3 {
                vertices.append(SCNVector3(centerLine[i].x + cc[j] * w, centerLine[i].y, centerLine[i].z + ss[j] * w))
            }
            
            w *= 0.90 // bolt gets thinner towards endings
        }
        
        // normal for each vertex: position vs. position of neighbor on next node
        var index1 = Int()
        var index2 = Int()
        
        func norm(_ v: SCNVector3) -> SCNVector3 {
            let d = max(sqrt(v.x * v.x + v.y * v.y + v.z * v.z), 0.0001)
            return SCNVector3(v.x / d, v.y / -d, v.z / d)
        }
        
        for i  in 0 ..< numSegments {
            for j in 0 ..< 3 {
                index1 = i * 3 + j      // point on current node
                index2 = index1 + 3     // neighboring point on next node
                if index2 >= vertices.count { index2 -= 6 } // last node references previous node instead
                
                normals.append(norm(vertices[index1].minus(vertices[index2])))
            }
        }
        
        
        let geoBolt = self.createGeometry(
            vertices: vertices,
            normals: normals,
            indices: indices,
            primitiveType: SCNGeometryPrimitiveType.triangles)
        
        
        let boltMaterial : SCNMaterial = {
            let material = SCNMaterial()
            material.name                       = "bolt"
            material.diffuse.contents           = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
            material.roughness.contents         = 1.0
            material.emission.contents          = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
            material.lightingModel              = .physicallyBased
            material.isDoubleSided              = true
            material.transparency               = 0.0
            return material
        }()
        
        geoBolt.firstMaterial = boltMaterial
        
        // this makes the bolt not appearing all geometry at the same time - it's an animation effect
        DispatchQueue.main.asyncAfter(deadline: .now() + LightningStrike.delayTime) {
            boltMaterial.transparency = 1.0
        }
        
        LightningStrike.delayTime += 0.01665
        
        // geoBolt.subdivisionLevel = 1 // give it a try or not...
        
        return geoBolt
        
    }
    
    
    
    
    // Creates a Branch of the entire Bolt
    func createBolt(_ nstart:SCNVector3, _ nend:SCNVector3) {
        start = nstart
        end = nend
        face = SCNNode(geometry:createNode())
        
        // This will add some glow around the Bolt,
        // but it is **enourmous** performence and memory intense,
        // you could try to add some SCNTechnique instead
        // let gaussianBlur    = CIFilter(name: "CIGaussianBlur")
        // gaussianBlur?.name  = "blur"
        // gaussianBlur?.setValue(2, forKey: "inputRadius")
        // face.filters        = [gaussianBlur] as? [CIFilter]
        
        sceneView.scene?.rootNode.addChildNode(face)
    }
}
class Geometry : NSObject {
    internal func createGeometry(
        vertices:[SCNVector3],
        normals:[SCNVector3],
        indices:[Int32],
        primitiveType:SCNGeometryPrimitiveType) -> SCNGeometry
    {
        
        // Computed property that indicates the number of primitives to create based on primitive type
        var primitiveCount:Int {
            get {
                switch primitiveType {
                case SCNGeometryPrimitiveType.line:
                    return indices.count / 2
                case SCNGeometryPrimitiveType.point:
                    return indices.count
                case SCNGeometryPrimitiveType.triangles,
                     SCNGeometryPrimitiveType.triangleStrip:
                    return indices.count / 3
                default : return 0
                }
            }
        }
        
        //------------------------
        let vdata = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count)
        
        let vertexSource = SCNGeometrySource(
            data: vdata as Data,
            semantic: SCNGeometrySource.Semantic.vertex,
            vectorCount: vertices.count,
            usesFloatComponents: true,
            componentsPerVector: 3,
            bytesPerComponent: MemoryLayout<Float>.size,
            dataOffset: 0,
            dataStride:  MemoryLayout<SCNVector3>.size)
        
        //------------------------
        let ndata = NSData(bytes: normals, length: MemoryLayout<SCNVector3>.size * normals.count)
        
        let normalSource = SCNGeometrySource(
            data: ndata as Data,
            semantic: SCNGeometrySource.Semantic.normal,
            vectorCount: normals.count,
            usesFloatComponents: true,
            componentsPerVector: 3,
            bytesPerComponent: MemoryLayout<Float>.size,
            dataOffset: 0,
            dataStride:  MemoryLayout<SCNVector3>.size)

        let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
        let element = SCNGeometryElement(
            data: indexData as Data, primitiveType: primitiveType,
            primitiveCount: primitiveCount, bytesPerIndex: MemoryLayout<Int32>.size)
        
        return SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
    }
}
extension SCNVector3
{
    func length() -> Float { return sqrtf(x*x + y*y + z*z) }
    func minus(_ other:SCNVector3) -> SCNVector3 { return SCNVector3(x - other.x, y - other.y, z - other.z) }
    
    func normalized() -> SCNVector3 {
        let len = length()
        var ans = SCNVector3()
        ans.x = self.x / len
        ans.y = self.y / len
        ans.z = self.z / len
        return ans
    }
}
extension UIColor {
    public convenience init?(hex: String) {
        let r, g, b, a: CGFloat
        
        if hex.hasPrefix("#") {
            let start = hex.index(hex.startIndex, offsetBy: 1)
            let hexColor = String(hex[start...])
            
            if hexColor.count == 8 {
                let scanner = Scanner(string: hexColor)
                var hexNumber: UInt64 = 0
                
                if scanner.scanHexInt64(&hexNumber) {
                    r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
                    g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
                    b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
                    a = CGFloat(hexNumber & 0x000000ff) / 255
                    
                    self.init(red: r, green: g, blue: b, alpha: a)
                    return
                }
            }
        }
        
        return nil
    }
}
对于UIColor

class LightningStrike : Geometry {
    
    var bolt:[Lightning] = []
    
    var start = SCNVector3() // stores start position of the Bolt
    var end = SCNVector3() // stores end position of the Bolt
    
    static var delayTime = 0.0
    
    override init() {
        start = SCNVector3(0.0, +5.0, 0.0) // default, to be changed
        end   = SCNVector3(0.0, -5.0, 0.0) // default, to be changed
        
        print("Lightning Strike initialized")
    }
    
    private func fadeOutBolt() {
        for b in bolt {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 2.0
            b.face.geometry?.firstMaterial?.transparency = 0.0
            SCNTransaction.commit()
        }
    }
    
    func strike() {
        for b in bolt { b.face.removeFromParentNode() }
        bolt.removeAll()
        
        // Create Main Bolt
        bolt.append(Lightning())
        bolt[0].createBolt(start,end)
        sceneView.scene?.rootNode.addChildNode(bolt[0].face)
        
        // Create child Bolts
        for _ in 0 ..< 15 { // number of child bolts
            // let parent = Int.random(in: 0 ..< bolt.count)  // random parent bolt, an other method
            let parent : Int = 0
            
            let start = bolt[parent].centerLine[10 + Int.random(in: 0 ..< 15)] // random node to start from off of parent, pay attention to: numSegments - changing numbers here can cause out of index crash
            let length:SCNVector3 = bolt[parent].end.minus(start) // length from our start to end of parent
            var end = SCNVector3()
            end.x = start.x + length.x / 1.5 + Float.random(in: 0 ... abs(length.x) / 3) // adjust by playing with this numbers
            end.y = start.y + length.y / 1.5 + Float.random(in: 0 ... abs(length.y) / 3) // adjust by playing with this numbers
            end.z = start.z + length.z / 1.5 + Float.random(in: 0 ... abs(length.z) / 3) // adjust by playing with this numbers

            bolt.append(Lightning())
            let index = bolt.count-1
            bolt[index].width = bolt[parent].width * 0.2
            bolt[index].deviation = bolt[parent].deviation * 0.3
            bolt[index].createBolt(start,end)
            sceneView.scene?.rootNode.addChildNode(bolt[0].face)
        }
        
        // Reset delay time and schedule fadeOut
        LightningStrike.delayTime = 0.0 // reset delay time
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { self.fadeOutBolt() }
        
        // Here you can add a Sound Effect
    }
    
    deinit {
        for b in bolt { b.face.removeFromParentNode() }
        bolt.removeAll()
        print("Lightning Strike deinitialized")
    }
    
}
class Lightning : Geometry {
    let UNASSIGNED:Float = 999
    var start = SCNVector3()
    var end = SCNVector3()
    var numSegments = Int() // use => 3,5,9,17,33,65
    var width = Float()
    var deviation = Float()
    var vertices:[SCNVector3] = []
    var normals:[SCNVector3] = []
    var indices:[Int32] = []
    var centerLine:[SCNVector3] = []
    var face:SCNNode! = nil
    
    override init() {
        
        numSegments = 33 // 17
        width = 0.1
        deviation = 1.5
        
        centerLine = Array(repeating: SCNVector3(), count: numSegments)
        
        // indexed indices never change
        var j:Int = 0
        for i  in 0 ..< numSegments-1 {
            j = i * 3
            indices.append(Int32(j + 0))  // 2 triangles on side #1
            indices.append(Int32(j + 2))
            indices.append(Int32(j + 3))
            indices.append(Int32(j + 2))
            indices.append(Int32(j + 5))
            indices.append(Int32(j + 3))
            
            indices.append(Int32(j + 2))  // side #2
            indices.append(Int32(j + 1))
            indices.append(Int32(j + 5))
            indices.append(Int32(j + 1))
            indices.append(Int32(j + 4))
            indices.append(Int32(j + 5))
            
            indices.append(Int32(j + 1))  // side #3
            indices.append(Int32(j + 0))
            indices.append(Int32(j + 4))
            indices.append(Int32(j + 0))
            indices.append(Int32(j + 3))
            indices.append(Int32(j + 4))
        }
    }
    
    func createNode() -> SCNGeometry {
        for i  in 0 ..< numSegments { centerLine[i].x = UNASSIGNED }
        centerLine[0] = start
        centerLine[numSegments-1] = end
        
        var hop:Int = max(numSegments / 2,1)
        var currentDeviation = deviation
        while true {
            for i in stride(from:0, to: numSegments, by:hop) {
                if centerLine[i].x != UNASSIGNED { continue }
                let p1 = centerLine[i-hop]
                let p2 = centerLine[i+hop]
                centerLine[i] = SCNVector3(
                    (p1.x + p2.x)/2 + Float.random(in: -currentDeviation ... currentDeviation),
                    (p1.y + p2.y)/2 + Float.random(in: -currentDeviation ... currentDeviation),
                    (p1.z + p2.z)/2 + Float.random(in: -currentDeviation ... currentDeviation))
            }
            
            if hop == 1 { break }
            hop /= 2
            currentDeviation *= 0.6
        }
        
        vertices.removeAll()
        normals.removeAll()
        
        // triangle of vertices at each centerLine node on XZ plane
        let ss:[Float] = [ sin(0), sin(Float.pi * 2/3), sin(Float.pi * 4/3)]
        let cc:[Float] = [ cos(0), cos(Float.pi * 2/3), cos(Float.pi * 4/3)]
        
        var w = width
        for i  in 0 ..< numSegments {
            for j in 0 ..< 3 {
                vertices.append(SCNVector3(centerLine[i].x + cc[j] * w, centerLine[i].y, centerLine[i].z + ss[j] * w))
            }
            
            w *= 0.90 // bolt gets thinner towards endings
        }
        
        // normal for each vertex: position vs. position of neighbor on next node
        var index1 = Int()
        var index2 = Int()
        
        func norm(_ v: SCNVector3) -> SCNVector3 {
            let d = max(sqrt(v.x * v.x + v.y * v.y + v.z * v.z), 0.0001)
            return SCNVector3(v.x / d, v.y / -d, v.z / d)
        }
        
        for i  in 0 ..< numSegments {
            for j in 0 ..< 3 {
                index1 = i * 3 + j      // point on current node
                index2 = index1 + 3     // neighboring point on next node
                if index2 >= vertices.count { index2 -= 6 } // last node references previous node instead
                
                normals.append(norm(vertices[index1].minus(vertices[index2])))
            }
        }
        
        
        let geoBolt = self.createGeometry(
            vertices: vertices,
            normals: normals,
            indices: indices,
            primitiveType: SCNGeometryPrimitiveType.triangles)
        
        
        let boltMaterial : SCNMaterial = {
            let material = SCNMaterial()
            material.name                       = "bolt"
            material.diffuse.contents           = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
            material.roughness.contents         = 1.0
            material.emission.contents          = UIColor.init(hex: "#BAB1FFFF") // this is a very clear, almost white purple
            material.lightingModel              = .physicallyBased
            material.isDoubleSided              = true
            material.transparency               = 0.0
            return material
        }()
        
        geoBolt.firstMaterial = boltMaterial
        
        // this makes the bolt not appearing all geometry at the same time - it's an animation effect
        DispatchQueue.main.asyncAfter(deadline: .now() + LightningStrike.delayTime) {
            boltMaterial.transparency = 1.0
        }
        
        LightningStrike.delayTime += 0.01665
        
        // geoBolt.subdivisionLevel = 1 // give it a try or not...
        
        return geoBolt
        
    }
    
    
    
    
    // Creates a Branch of the entire Bolt
    func createBolt(_ nstart:SCNVector3, _ nend:SCNVector3) {
        start = nstart
        end = nend
        face = SCNNode(geometry:createNode())
        
        // This will add some glow around the Bolt,
        // but it is **enourmous** performence and memory intense,
        // you could try to add some SCNTechnique instead
        // let gaussianBlur    = CIFilter(name: "CIGaussianBlur")
        // gaussianBlur?.name  = "blur"
        // gaussianBlur?.setValue(2, forKey: "inputRadius")
        // face.filters        = [gaussianBlur] as? [CIFilter]
        
        sceneView.scene?.rootNode.addChildNode(face)
    }
}
class Geometry : NSObject {
    internal func createGeometry(
        vertices:[SCNVector3],
        normals:[SCNVector3],
        indices:[Int32],
        primitiveType:SCNGeometryPrimitiveType) -> SCNGeometry
    {
        
        // Computed property that indicates the number of primitives to create based on primitive type
        var primitiveCount:Int {
            get {
                switch primitiveType {
                case SCNGeometryPrimitiveType.line:
                    return indices.count / 2
                case SCNGeometryPrimitiveType.point:
                    return indices.count
                case SCNGeometryPrimitiveType.triangles,
                     SCNGeometryPrimitiveType.triangleStrip:
                    return indices.count / 3
                default : return 0
                }
            }
        }
        
        //------------------------
        let vdata = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count)
        
        let vertexSource = SCNGeometrySource(
            data: vdata as Data,
            semantic: SCNGeometrySource.Semantic.vertex,
            vectorCount: vertices.count,
            usesFloatComponents: true,
            componentsPerVector: 3,
            bytesPerComponent: MemoryLayout<Float>.size,
            dataOffset: 0,
            dataStride:  MemoryLayout<SCNVector3>.size)
        
        //------------------------
        let ndata = NSData(bytes: normals, length: MemoryLayout<SCNVector3>.size * normals.count)
        
        let normalSource = SCNGeometrySource(
            data: ndata as Data,
            semantic: SCNGeometrySource.Semantic.normal,
            vectorCount: normals.count,
            usesFloatComponents: true,
            componentsPerVector: 3,
            bytesPerComponent: MemoryLayout<Float>.size,
            dataOffset: 0,
            dataStride:  MemoryLayout<SCNVector3>.size)

        let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
        let element = SCNGeometryElement(
            data: indexData as Data, primitiveType: primitiveType,
            primitiveCount: primitiveCount, bytesPerIndex: MemoryLayout<Int32>.size)
        
        return SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
    }
}
extension SCNVector3
{
    func length() -> Float { return sqrtf(x*x + y*y + z*z) }
    func minus(_ other:SCNVector3) -> SCNVector3 { return SCNVector3(x - other.x, y - other.y, z - other.z) }
    
    func normalized() -> SCNVector3 {
        let len = length()
        var ans = SCNVector3()
        ans.x = self.x / len
        ans.y = self.y / len
        ans.z = self.z / len
        return ans
    }
}
extension UIColor {
    public convenience init?(hex: String) {
        let r, g, b, a: CGFloat
        
        if hex.hasPrefix("#") {
            let start = hex.index(hex.startIndex, offsetBy: 1)
            let hexColor = String(hex[start...])
            
            if hexColor.count == 8 {
                let scanner = Scanner(string: hexColor)
                var hexNumber: UInt64 = 0
                
                if scanner.scanHexInt64(&hexNumber) {
                    r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
                    g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
                    b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
                    a = CGFloat(hexNumber & 0x000000ff) / 255
                    
                    self.init(red: r, green: g, blue: b, alpha: a)
                    return
                }
            }
        }
        
        return nil
    }
}
用法: 初始化ViewController中的类,如下所示:

let lightningStrike = LightningStrike()
还添加了轻触手势识别器(在viewDidLoad中),以便于测试:

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
sceneView.addGestureRecognizer(tapGesture)
以及触发闪电的相应功能:

@objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    lightningStrike.strike() // will fire a Lighting Bolt
}
结果:


玩得开心。

谢谢。对于分支,您将如何执行“旋转(方向、角度)*长度比例+中点”操作?我找不到如何旋转分支line@Junaid在这里,您可以找到如何旋转二维向量