Scenekit: Почему SCNNode не должен быть разделен на подклассы? - PullRequest
0 голосов
/ 04 мая 2018

Я продолжаю читать в StackOverflow, что мы не должны подкласс SCNNode, может кто-нибудь указать мне правильное направление для лучших практик Scenekit?

Мне кажется, что создание подклассов SCNNode поможет мне использовать специальные методы для различных типов подклассов. Следуя объектно-ориентированному программированию ... возможно, 3d-автомобиль, который является подклассом SCNNode, может иметь методы для запуска двигателя, перемещения, открытия дверей и т. Д.

Если это не правильный путь ... как можно связать узлы SCNNode с дополнительными свойствами и методами? А также, как можно отличить узлы SCN, которые являются автомобилями, от узлов SCN, которые являются грузовиками, самолетами или чем-то еще?

1 Ответ

0 голосов
/ 04 мая 2018

Лично я не вижу ничего плохого в подклассе SCNNode, в зависимости, конечно, от того, зачем вам это нужно.

Ключевое соображение здесь следующее:

Если вы добавляете функции общего назначения, которые должны быть доступны для каждого SCNNode, затем сделайте расширение.

Все экземпляры SCNNode могут затем вызывать эти новые методы.

С другой стороны:

Если вы добавляете функциональность, которая должна быть ограничена специальными экземпляры SCNNode, и вам необходимо определить их конкретно: затем сделать подкласс, так как только новые экземпляры могут использовать ваш новый методы.

Если вы решили использовать расширение SCNNode, это будет означать, что любые создаваемые вами функции могут быть применены к любому SCNNode.

Скажем, например, что вы хотели бы позволить любому SCNNode расти и уменьшаться, тогда extension будет вашей лучшей ставкой, например:

extension SCNNode{

    /// Doubles The Size Of The SCNNode & Then Returns It To Its Original Size
    func growAndShrink(){

        //1. Create An SCNAction Which Will Double The Size Of Our Node
        let growAction = SCNAction.scale(by: 2, duration: 5)

        //2. Create Another SCNAction Wjich Will Revert Our Node Back To It's Original Size
        let shrinkAction = SCNAction.scale(by: 0.5, duration: 5)

        //3. Create An Animation Sequence Which Will Store Our Actions
        let animationSequence = SCNAction.sequence([growAction, shrinkAction])

        //4. Run The Sequence
        self.runAction(animationSequence)

    }

}

Однако, если вы хотите, например, создать SCNNode, у которого есть функции, которые были бы доступны только для этого экземпляра, то создание subclass может быть способом продвижения вперед.

Скажем так, что нам нужно было создать SCNNode с SCNPlaneGeometry, который предоставил бы нам конкретную информацию об этом узле, тогда мы могли бы создать подкласс следующим образом:

class PlaneNode: SCNNode {

    let DEFAULT_IMAGE: String = "defaultGrid"
    let NAME: String = "PlaneNode"
    var planeGeometry: SCNPlane
    var planeAnchor: ARPlaneAnchor

    var widthInfo: String!
    var heightInfo: String!
    var alignmentInfo: String!

    //---------------
    //MARK: LifeCycle
    //---------------

    /// Inititialization
    ///
    /// - Parameters:
    ///   - anchor: ARPlaneAnchor
    ///   - node: SCNNode
    ///   - node: Bool
    init(anchor: ARPlaneAnchor, node: SCNNode, image: Bool, identifier: Int, opacity: CGFloat = 0.25){

        //1. Create The SCNPlaneGeometry
        self.planeAnchor = anchor
        self.planeGeometry = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
        let planeNode = SCNNode(geometry: planeGeometry)

        super.init()

        //2. If The Image Bool Is True We Use The Default Image From The Assets Bundle
        let planeMaterial = SCNMaterial()

        if image{

            planeMaterial.diffuse.contents = UIImage(named: DEFAULT_IMAGE)

        }else{

            planeMaterial.diffuse.contents = UIColor.cyan
        }

        //3. Set The Geometries Contents
        self.planeGeometry.materials = [planeMaterial]

        //4. Set The Position Of The PlaneNode
        planeNode.simdPosition = float3(self.planeAnchor.center.x, 0, self.planeAnchor.center.z)

        //5. Rotate It On It's XAxis
        planeNode.eulerAngles.x = -.pi / 2

        //6. Set The Opacity Of The Node
        planeNode.opacity = opacity

        //7. Add The PlaneNode
        node.addChildNode(planeNode)

        //8. Set The Nodes ID
        node.name = "\(NAME) \(identifier)"

    }

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


    /// Updates The Size Of The Plane As & When The ARPlaneAnchor Has Been Updated
    ///
    /// - Parameter anchor: ARPlaneAnchor
    func update(_ anchor: ARPlaneAnchor) {

        self.planeAnchor = anchor

        self.planeGeometry.width = CGFloat(anchor.extent.x)
        self.planeGeometry.height = CGFloat(anchor.extent.z)

        self.position = SCNVector3Make(anchor.center.x, 0.01, anchor.center.z)

        returnPlaneInfo()
    }

    //-----------------------
    //MARK: Plane Information
    //-----------------------

    /// Returns The Size Of The ARPlaneAnchor & Its Alignment
    func returnPlaneInfo(){

        let widthOfPlane = self.planeAnchor.extent.x
        let heightOfPlane = self.planeAnchor.extent.z

        var planeAlignment: String!

        switch planeAnchor.alignment {

        case .horizontal:
            planeAlignment = "Horizontal"
        case .vertical:
            planeAlignment = "Vertical"
        }

        #if DEBUG
        print("""
            Width Of Plane =  \(String(format: "%.2fm", widthOfPlane))
            Height Of Plane =  \(String(format: "%.2fm", heightOfPlane))
            Plane Alignment = \(planeAlignment)
            """)
        #endif

        self.widthInfo = String(format: "%.2fm", widthOfPlane)
        self.heightInfo = String(format: "%.2fm", heightOfPlane)
        self.alignmentInfo = planeAlignment
    }

}

В вашем случае кажется, что, поскольку вы планируете иметь очень конкретные случаи, например, грузовики, самолеты и т. д., каждый со своими специфическими функциями, тогда использование подкласса SCNNode может стать выходом из положения.

Надеюсь, это поможет ...

Обновление: По вашему запросу, например. Как это будет работать в случае использования .scn file?

Некоторые pseudo code могут выглядеть так:

/// Creates & Manages The Car Model
class Car: SCNNode {

    let MODEL_SCALE = SCNVector3(0.5, 0.5, 0.5)
    let MODEL_POSITION = SCNVector3(1, 0, -2.5)
    let MODEL_ROTATION: CGFloat = 30.45
    let TURN_DURATION: Double = 1

    var leftFrontWheel: SCNNode!
    var rightFrontWheel: SCNNode!
    var leftBackWheel: SCNNode!
    var rightBackWheel: SCNNode!

    //--------------------
    //MARK: Initialization
    //--------------------

    override init() {

        super.init()

        //1. Get The Car Model From The Assetts Bundle
        guard let carModel = SCNScene(named: "StackOverflow.scnassets/Models/Car.scn"),
            let modelNode = carModel.rootNode.childNode(withName: "Root", recursively: false),
            let frontLeftWheel = modelNode.childNode(withName: "leftFront", recursively: false),
            let frontRightWheel = modelNode.childNode(withName: "rightFront", recursively: false),
            let rearLeftWheel = modelNode.childNode(withName: "leftRear", recursively: false),
            let rearRightWheel = modelNode.childNode(withName: "rightRear", recursively: false) else { return }


        //2. Scale, Rotate & Position The Car
        self.scale = MODEL_SCALE
        self.simdRotation = simd_float4 (0, 1, 0, Float(MODEL_ROTATION.degreesToRadians))
        self.position = MODEL_POSITION

        //2. Create A Reference To Each Wheel
        self.leftFrontWheel = frontLeftWheel
        self.rightFrontWheel = frontRightWheel
        self.leftBackWheel = rearLeftWheel
        self.rightBackWheel = rearRightWheel


        //3. Add The Car To The Root Node
        self.addChildNode(modelNode)

        print("""
            Loaded Car Model
            Scale = \(MODEL_SCALE)
            Rotation = \(MODEL_ROTATION)
            Position = \(MODEL_POSITION)
            """)

    }

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

    //---------------
    //MARK: Animation
    //---------------

    /// Runs The Wheel Animation
    func animateWheels(){

        let wheelTurnAnimationOut = SCNAction.rotate(toAxisAngle:  SCNVector4(0 , 0 , 1, CGFloat(45).degreesToRadians), duration: TURN_DURATION)
        let wheelTurnAnimationIn = SCNAction.rotate(toAxisAngle:  SCNVector4(0 , 0 , 1, CGFloat(0).degreesToRadians), duration: TURN_DURATION)
        let turningSequence = SCNAction.sequence([wheelTurnAnimationOut, wheelTurnAnimationIn])
        let turningAction = SCNAction.repeatForever(turningSequence)
        leftFrontWheel.runAction(turningAction)
        rightFrontWheel.runAction(turningAction)
        leftBackWheel.runAction(turningAction)
        rightBackWheel.runAction(turningAction)

    }

}

Что вы можете затем инициализировать и управлять функциями следующим образом:

 let car = Car()
 self.augmentedRealityView.scene.rootNode.addChildNode(car)
 car.animateWheels()

Надеюсь, это поможет ...

...