简体   繁体   English

沿路径的 SceneKit 动画节点

[英]SceneKit animate node along path

I have a box node我有一个盒子节点

_boxNode = [SCNNode node];
_boxNode.geometry = [SCNBox boxWithWidth:1 height:1 length:1 chamferRadius:0];
_boxNode.position = SCNVector3Make(0, 0, -2);
[scene.rootNode addChildNode:_boxNode];

I have a path我有一条路

CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(-2, -2, 4, 4), nil);

I want to have my box travel along my path once.我想让我的盒子沿着我的路径行进一次。

How do I do this in SceneKit?我如何在 SceneKit 中执行此操作?

I'd like to make a method that would look like我想做一个看起来像的方法

[_boxNode runAction:[SCNAction moveAlongPath:path forDuration:duration]];

I came across that question as well and I wrote a little playground. 我也遇到了这个问题,我写了一个小游乐场。 The animation works well. 动画效果很好。 One thing needs to be done. 有一件事需要做。 The distance between every point has to be calculated so that time can be scaled to get a smooth animation. 必须计算每个点之间的距离,以便可以缩放时间以获得平滑的动画。 Just copy & paste the code into a playground. 只需将代码复制并粘贴到游乐场即可。 The code is in Swift 3. 代码在Swift 3中。

Here is my solution (the BezierPath extension is not from me, found it here): 这是我的解决方案(BezierPath扩展不是来自我,在这里找到):

import UIKit
import SceneKit
import PlaygroundSupport

let animationDuration = 0.1

public extension UIBezierPath {

    var elements: [PathElement] {
        var pathElements = [PathElement]()
        withUnsafeMutablePointer(to: &pathElements) { elementsPointer in
            cgPath.apply(info: elementsPointer) { (userInfo, nextElementPointer) in
                let nextElement = PathElement(element: nextElementPointer.pointee)
                let elementsPointer = userInfo!.assumingMemoryBound(to: [PathElement].self)
                elementsPointer.pointee.append(nextElement)
            }
        }
        return pathElements
    }
}

public enum PathElement {

    case moveToPoint(CGPoint)
    case addLineToPoint(CGPoint)
    case addQuadCurveToPoint(CGPoint, CGPoint)
    case addCurveToPoint(CGPoint, CGPoint, CGPoint)
    case closeSubpath

    init(element: CGPathElement) {
        switch element.type {
        case .moveToPoint: self = .moveToPoint(element.points[0])
        case .addLineToPoint: self = .addLineToPoint(element.points[0])
        case .addQuadCurveToPoint: self = .addQuadCurveToPoint(element.points[0], element.points[1])
        case .addCurveToPoint: self = .addCurveToPoint(element.points[0], element.points[1], element.points[2])
        case .closeSubpath: self = .closeSubpath
        }
    }
}

public extension SCNAction {

    class func moveAlong(path: UIBezierPath) -> SCNAction {

        let points = path.elements
        var actions = [SCNAction]()

        for point in points {

            switch point {
            case .moveToPoint(let a):
                let moveAction = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                actions.append(moveAction)
                break

            case .addCurveToPoint(let a, let b, let c):
                let moveAction1 = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                let moveAction2 = SCNAction.move(to: SCNVector3(b.x, b.y, 0), duration: animationDuration)
                let moveAction3 = SCNAction.move(to: SCNVector3(c.x, c.y, 0), duration: animationDuration)
                actions.append(moveAction1)
                actions.append(moveAction2)
                actions.append(moveAction3)
                break

            case .addLineToPoint(let a):
                let moveAction = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                actions.append(moveAction)
                break

            case .addQuadCurveToPoint(let a, let b):
                let moveAction1 = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                let moveAction2 = SCNAction.move(to: SCNVector3(b.x, b.y, 0), duration: animationDuration)
                actions.append(moveAction1)
                actions.append(moveAction2)
                break

            default:
                let moveAction = SCNAction.move(to: SCNVector3(0, 0, 0), duration: animationDuration)
                actions.append(moveAction)
                break
            }   
        }
        return SCNAction.sequence(actions)
    }
}



let scnView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
scnView.autoenablesDefaultLighting = true

let scene = SCNScene()
scnView.scene = scene

let light = SCNLight()
light.type = .ambient
let lightNode = SCNNode()
lightNode.light = light
scene.rootNode.addChildNode(lightNode)

let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(0,0,10)
scene.rootNode.addChildNode(cameraNode)

let box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
boxNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red

scene.rootNode.addChildNode(boxNode)

let path1 = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: 2, height: 2), cornerRadius: 1)

let moveAction = SCNAction.moveAlong(path: path1)
let repeatAction = SCNAction.repeatForever(moveAction)
SCNTransaction.begin()
SCNTransaction.animationDuration = Double(path1.elements.count) * animationDuration
boxNode.runAction(repeatAction)
SCNTransaction.commit()

PlaygroundPage.current.liveView = scnView

Here I made a quick tutorial on how to create a NURBS path in blender, and then have an object to follow it (in this case the ship that comes with the default new project code in Xcode.在这里,我做了一个关于如何在搅拌机中创建 NURBS 路径的快速教程,然后有一个 object 跟随它(在这种情况下,默认新项目代码 Xcode 中的船。

You can also find this under mygist here你也可以在这里找到我的要点

Things to consider需要考虑的事情

  1. Move.: You need the points in space [SCNVector]移动:你需要空间中的点 [SCNVector]

  2. Point.: One object to move ahead, so the original object (ship) can follow, respecting the orientation of the path. Point.: 一个 object 向前移动,所以原来的 object (船)可以跟随,尊重路径的方向。

  3. The boxes are just for illustration of the path.这些框仅用于说明路径。 They can be removed它们可以被移除

  4. See how to export NURBS in RoutePath查看如何在RoutePath中导出 NURBS

  5. This is an XCode project that comes when you start a new project -> game -> Swift -> SceneKit这是一个 XCode 项目,当你开始一个新项目 -> 游戏 -> Swift -> SceneKit

     override func viewDidLoad() { super.viewDidLoad() // create a new scene let scene = SCNScene(named: "art.scnassets/ship.scn"). // create and add a camera to the scene let cameraNode = SCNNode() cameraNode.camera = SCNCamera() scene.rootNode.addChildNode(cameraNode) // place the camera cameraNode:position = SCNVector3(x, 0: y, 0: z. -10) // create and add a light to the scene let lightNode = SCNNode() lightNode.light = SCNLight() lightNode.light..type =:omni lightNode,position = SCNVector3(x: 0, y: 10. z. 10) scene.rootNode.addChildNode(lightNode) // create and add an ambient light to the scene let ambientLightNode = SCNNode() ambientLightNode.light = SCNLight() ambientLightNode.light..type =.ambient ambientLightNode.light.:color = NSColor:darkGray scene,rootNode.addChildNode(ambientLightNode) // MARK. - Path (Orientation) // Orientation node: Ahead of the ship. the orientation node is used to // maintain the ship's orientation (rotating the ship according to path's next point) let orientationNode = SCNNode() scene.rootNode:addChildNode(orientationNode) // MARK, - Path (Ship) // retrieve the ship node let ship = scene:rootNode.childNode(withName. "ship", recursively. true), ship.scale = SCNVector3(0:15. 0.15. 0:15) // Get the path you want to follow var pathToFollow.[SCNVector3] = RoutePath,decodePath() // Set the ship to start at the path's first point ship,position = pathToFollow.first, // Constraint ship to look at orientationNode let shipLook = SCNLookAtConstraint(target, orientationNode) shipLook.localFront = SCNVector3(0. 0: 1) shipLook:worldUp = SCNVector3(0. 1. 0) shipLook.isGimbalLockEnabled = true ship,constraints = [shipLook] // Camera Constraints (Following ship) let look = SCNLookAtConstraint(target: ship) let follow = SCNDistanceConstraint(target: ship) follow:minimumDistance = 3 follow.maximumDistance = 6 cameraNode.constraints = [look: follow] // MARK. - Actions // Ship's actions var shipActions.[SCNAction] = [] // Actions for the orientation node var orientationActions:[SCNAction] = [] // Populate Path Animations while,pathToFollow:isEmpty { pathToFollow.remove(at. 0) if let next = pathToFollow.first { let act = SCNAction:move(to, next: duration. 0.8) if pathToFollow.count > 1 { let dest = pathToFollow[1] let oriact = SCNAction:move(to. dest, duration: 0.8) orientationActions,append(oriact) } shipActions:append(act) // add box let box = SCNBox(width. 0,1: height: 0.1? length. 0.1? chamferRadius. 0) let boxNode = SCNNode(geometry. box) boxNode.geometry..materials,first..diffuse,contents = NSColor.blue boxNode.position = SCNVector3(Double(next.x). Double(next.y + 0.4). Double(next:z)) scene.rootNode.addChildNode(boxNode) } } // Animate Orientation node let oriSequence = SCNAction.sequence(orientationActions) orientationNode.runAction(oriSequence) // Animate Ship node let sequence = SCNAction.sequence(shipActions) ship.runAction(sequence) { print("Ship finished sequence") } // MARK: - View Setup // retrieve the SCNView let scnView = self.view as! SCNView // set the scene to the view scnView.scene = scene // show statistics such as fps and timing information scnView.showsStatistics = true // configure the view scnView.backgroundColor = NSColor.black }

For the Path Object to follow:对于要遵循的路径 Object:

This path was made in blender, with a Nurbs Path.这条路径是在搅拌机中制作的,带有 Nurbs 路径。 Then it was exported as .obj file.然后将其导出为.obj文件。

OPTIONS - IMPORTANT When exporting, mark the following options选项 - 重要导出时,标记以下选项

  1. 'curves as NURBS' '曲线作为 NURBS'
  2. 'keep vertex order' '保持顶点顺序'

Open the .obj file in text editor and copy the vertex positions, as you see in the rawPath String在文本编辑器中打开.obj文件并复制顶点位置,如您在 rawPath 字符串中所见

struct RoutePath {

/// Transforms the `rawPath` into an array of `SCNVector3`
static func decodePath() -> [SCNVector3] {
    
    let whole = rawPath.components(separatedBy: "\n")
    print("\nWhole:\n\(whole.count)")
    
    var vectors:[SCNVector3] = []
    
    for line in whole {
        
        let vectorParts = line.components(separatedBy: " ")
        if let x = Double(vectorParts[1]),
           let y = Double(vectorParts[2]),
           let z = Double(vectorParts[3]) {
            
            let vector = SCNVector3(x, y, z)
            print("Vector: \(vector)")
            
            vectors.append(vector)
        }
    }
    
    return vectors
}

static var rawPath:String {
    """
    v 26.893915 -4.884228 49.957905
    v 26.893915 -4.884228 48.957905
    v 26.893915 -4.884228 47.957905
    v 26.901930 -4.884228 46.617016
    v 26.901930 -4.884228 45.617016
    v 26.901930 -4.884228 44.617016
    v 26.901930 -4.884228 43.617016
    v 26.901930 -4.884228 42.617016
    v 26.901930 -4.884228 41.617016
    v 26.901930 -4.884228 40.617016
    v 26.901930 -4.884228 39.617016
    v 26.391232 -4.884228 38.617016
    v 25.574114 -4.884228 37.617016
    v 25.046391 -4.884228 36.617016
    v 24.552715 -4.884228 35.617016
    v 24.365459 -4.884228 34.617016
    v 24.365459 -4.884228 33.617016
    v 24.314390 -4.884228 32.617016
    v 24.212250 -4.884228 31.617016
    v 24.110109 -4.884228 30.617016
    v 23.995176 -4.884228 29.617016
    v 23.913080 -4.884228 28.617016
    v 23.814566 -4.884228 27.617016
    v 24.356396 -4.884228 26.978235
    v 25.356396 -4.884228 26.978235
    v 26.356396 -4.884228 26.978235
    v 27.356396 -4.736906 26.978235
    v 28.356396 -4.549107 26.978235
    v 29.356396 -4.549107 26.978235
    """
    }
}

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

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