How and why I can not save a custom SCNnode using NSKeyedArchiver

103 views Asked by At

i have the following custom class "RBTerrain" of type SCNnode (find it online). I use this to create a custom terrain on my SCNScene.

This class allow my to create a custom geometry for Terrain SCNNode.

I want to save this SCNnode using NSKeyedArchiver in order to use it later.

First debug test:

I use the following code to save and read a simple SCNnode create with an SCNBox geometry and all working fine.


   func writeData(nodoToSaved: SCNNode) {
        let fixedFilename = String("nodo") // just a reference Name
        let fullPath = getDocumentsDirectory().appendingPathComponent(fixedFilename)
       
        
        do {
            guard let data = try? NSKeyedArchiver.archivedData(withRootObject: nodoToSaved, requiringSecureCoding: false)
                else { fatalError("can't encode data") }
            try data.write(to: fullPath)
            print("Saved Successufully")
            print("Full Path for write is: \(fullPath)")
        } catch {
            print("Couldn't write file")
        }
    }
    
    
    // Read Data Function
    func readData() {
        let fixedFilename = String("nodo")
        let fullPath = getDocumentsDirectory().appendingPathComponent(fixedFilename)
        print("Full Path for Read is: \(fullPath)")
        
        guard let data = try? Data(contentsOf: fullPath) else {
            print("no data found")
            return }

        do {
            guard let nodo = try NSKeyedUnarchiver.unarchivedObject(ofClass: SCNNode.self, from: data) else
            {
                
                return}
            self.rootNode.addChildNode(nodo)
        } catch let err{
            print("error can't decode the saved data \(err.localizedDescription)")
        }
    }
    
    
    
    
    
    
    // Helper Function
    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

I use the same code to save the SCNnode created with the custom geometry via the RBTerrain class and it fail to read the file with the following error

The data couldn’t be read because it isn’t in the correct format

Why swift fail to read it when I use a custom geometry to create the SCNnode? Is my custom geometry require "somethings" missing in order to be use with NSKeyedArchiver?

here my custom RBTerrain class


import Foundation
import SceneKit

typealias RBTerrainFormula = ((Int32, Int32) -> (Double))

// -----------------------------------------------------------------------------

class RBTerrain: SCNNode {
    private var _heightScale = 256
    private var _terrainWidth = 64
    private var _terrainLength = 64
    private var _terrainGeometry: SCNGeometry?
    private var _texture: UIImage?
    private var _color = UIColor.white
    
    var formula: RBTerrainFormula?
    
    // -------------------------------------------------------------------------
    // MARK: - Properties
    
    var length: Int {
        get {
            return _terrainLength
        }
    }
    
    // -------------------------------------------------------------------------
    
    var width: Int {
        get {
            return _terrainLength
        }
    }
    
    // -------------------------------------------------------------------------

    var texture: UIImage? {
        get {
            return _texture
        }
        set(value) {
            _texture = value
            
            if (_terrainGeometry != nil && _texture != nil) {
                let material = SCNMaterial()
                material.diffuse.contents = _texture!
                material.isLitPerPixel = true
                material.diffuse.magnificationFilter = .none
                material.diffuse.wrapS = .repeat
                material.diffuse.wrapT = .repeat
                material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(_terrainWidth*2), Float(_terrainLength*2), 1)
                
                _terrainGeometry!.firstMaterial = material
                _terrainGeometry!.firstMaterial!.isDoubleSided = true
            }
        }
    }
    
    // -------------------------------------------------------------------------
    
    var color: UIColor {
        get {
            return _color
        }
        set(value) {
            _color = value
            
            if (_terrainGeometry != nil) {
                let material = SCNMaterial()
                material.diffuse.contents = _color
                material.isLitPerPixel = true

                _terrainGeometry!.firstMaterial = material
                _terrainGeometry!.firstMaterial!.isDoubleSided = true
            }
        }
    }
    
    // -------------------------------------------------------------------------
    // MARK: - Terrain formula
    
    func valueFor(x: Int32, y: Int32) ->Double {
        if (formula == nil) {
            return 0.0
        }
        
        return formula!(x, y)
    }

    // -------------------------------------------------------------------------
    // MARK: - Geometry creation
    private func bkcreateGeometry() ->SCNGeometry {
        let cint: CInt = 0
        let sizeOfCInt = MemoryLayout.size(ofValue: cint)
        let float: Float = 0.0
        let sizeOfFloat = MemoryLayout.size(ofValue: float)
        let vec2: vector_float2 = vector2(0, 0)
        let sizeOfVecFloat = MemoryLayout.size(ofValue: vec2)

        let w: CGFloat = CGFloat(_terrainWidth)
        let h: CGFloat = CGFloat(_terrainLength)
        let scale: Double = Double(_heightScale)

        var sources = [SCNGeometrySource]()
        var elements = [SCNGeometryElement]()

        let maxElements: Int = _terrainWidth * _terrainLength * 4
        var vertices = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
        var normals = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
        var uvList: [vector_float2] = []
        
        var vertexCount = 0
        let factor: CGFloat = 0.5

        for y in 0...Int(h-2) {
            for x in 0...Int(w-1) {
                let topLeftZ = valueFor(x: Int32(x), y: Int32(y+1)) / scale
                let topRightZ = valueFor(x: Int32(x+1), y: Int32(y+1)) / scale
                let bottomLeftZ = valueFor(x: Int32(x), y: Int32(y)) / scale
                let bottomRightZ = valueFor(x: Int32(x+1), y: Int32(y)) / scale
                
                let topLeft = SCNVector3Make(Float(x)-Float(factor), Float(topLeftZ), Float(y)+Float(factor))
                let topRight = SCNVector3Make(Float(x)+Float(factor), Float(topRightZ), Float(y)+Float(factor))
                let bottomLeft = SCNVector3Make(Float(x)-Float(factor), Float(bottomLeftZ), Float(y)-Float(factor))
                let bottomRight = SCNVector3Make(Float(x)+Float(factor), Float(bottomRightZ), Float(y)-Float(factor))

                vertices[vertexCount] = bottomLeft
                vertices[vertexCount+1] = topLeft
                vertices[vertexCount+2] = topRight
                vertices[vertexCount+3] = bottomRight

                let xf = CGFloat(x)
                let yf = CGFloat(y)

                uvList.append(vector_float2(Float(xf/w), Float(yf/h)))
                uvList.append(vector_float2(Float(xf/w), Float((yf+factor)/h)))
                uvList.append(vector_float2(Float((xf+factor)/w), Float((yf+factor)/h)))
                uvList.append(vector_float2(Float((xf+factor)/w), Float(yf/h)))
                
                vertexCount += 4
                print(vertexCount)
            }
        }

        
        let source = SCNGeometrySource(vertices: vertices)
        sources.append(source)

        let geometryData = NSMutableData()

        var geometry: CInt = 0
        while (geometry < CInt(vertexCount)) {
            let bytes: [CInt] = [geometry, geometry+2, geometry+3, geometry, geometry+1, geometry+2]
            geometryData.append(bytes, length: sizeOfCInt*6)
            geometry += 4
        }

        let element = SCNGeometryElement(data: geometryData as Data, primitiveType: .triangles, primitiveCount: vertexCount/2, bytesPerIndex: sizeOfCInt)
        elements.append(element)
        
        for normalIndex in 0...vertexCount-1 {
            normals[normalIndex] = SCNVector3Make(0, 0, -1)
        }
        sources.append(SCNGeometrySource(normals: normals))

        let uvData = NSData(bytes: uvList, length: uvList.count * sizeOfVecFloat)
        let uvSource = SCNGeometrySource(data: uvData as Data, semantic: SCNGeometrySource.Semantic.texcoord, vectorCount: uvList.count, usesFloatComponents: true, componentsPerVector: 2, bytesPerComponent: sizeOfFloat, dataOffset: 0, dataStride: sizeOfVecFloat)
        sources.append(uvSource)
        
        _terrainGeometry = SCNGeometry(sources: sources, elements: elements)
        
        let material = SCNMaterial()
        material.isLitPerPixel = true
        material.diffuse.magnificationFilter = .none
        material.diffuse.wrapS = .repeat
        material.diffuse.wrapT = .repeat
        material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(_terrainWidth*2), Float(_terrainLength*2), 1)
        material.diffuse.intensity = 1.0

        _terrainGeometry!.firstMaterial = material
        _terrainGeometry!.firstMaterial!.isDoubleSided = true
        
        return _terrainGeometry!
    }
    private func createGeometry() ->SCNGeometry {
        let cint: CInt = 0
        let sizeOfCInt = MemoryLayout.size(ofValue: cint)
        let float: Float = 0.0
        let sizeOfFloat = MemoryLayout.size(ofValue: float)
        let vec2: vector_float2 = vector2(0, 0)
        let sizeOfVecFloat = MemoryLayout.size(ofValue: vec2)

        let w: CGFloat = CGFloat(_terrainWidth)
        let h: CGFloat = CGFloat(_terrainLength)
        let scale: Double = Double(_heightScale)

        var sources = [SCNGeometrySource]()
        var elements = [SCNGeometryElement]()

        let maxElements: Int = _terrainWidth * _terrainLength * 4
        var vertices = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
        var normals = [SCNVector3](repeating:SCNVector3Zero, count:maxElements)
        var uvList: [vector_float2] = []
        
        var vertexCount = 0
        let factor: CGFloat = 0.5
   
        for y in 0...Int(h-2) {
            for x in 0...Int(w-1) {
                let topLeftZ = valueFor(x: Int32(x), y: Int32(y+1)) / scale
                let topRightZ = valueFor(x: Int32(x+1), y: Int32(y+1)) / scale
                let bottomLeftZ = valueFor(x: Int32(x), y: Int32(y)) / scale
                let bottomRightZ = valueFor(x: Int32(x+1), y: Int32(y)) / scale
                
                let topLeft = SCNVector3Make(Float(x)-Float(factor), Float(topLeftZ), Float(y)+Float(factor))
                let topRight = SCNVector3Make(Float(x)+Float(factor), Float(topRightZ), Float(y)+Float(factor))
                let bottomLeft = SCNVector3Make(Float(x)-Float(factor), Float(bottomLeftZ), Float(y)-Float(factor))
                let bottomRight = SCNVector3Make(Float(x)+Float(factor), Float(bottomRightZ), Float(y)-Float(factor))

                vertices[vertexCount] = bottomLeft
                vertices[vertexCount+1] = topLeft
                vertices[vertexCount+2] = topRight
                vertices[vertexCount+3] = bottomRight

                let xf = CGFloat(x)
                let yf = CGFloat(y)

                uvList.append(vector_float2(Float(xf/w), Float(yf/h)))
                uvList.append(vector_float2(Float(xf/w), Float((yf+factor)/h)))
                uvList.append(vector_float2(Float((xf+factor)/w), Float((yf+factor)/h)))
                uvList.append(vector_float2(Float((xf+factor)/w), Float(yf/h)))
                
                vertexCount += 4
            }
        }

        let source = SCNGeometrySource(vertices: vertices)
        sources.append(source)

        let geometryData = NSMutableData()

        var geometry: CInt = 0
        while (geometry < CInt(vertexCount)) {
            let bytes: [CInt] = [geometry, geometry+2, geometry+3, geometry, geometry+1, geometry+2]
            geometryData.append(bytes, length: sizeOfCInt*6)
            geometry += 4
        }

        let element = SCNGeometryElement(data: geometryData as Data, primitiveType: .triangles, primitiveCount: vertexCount/2, bytesPerIndex: sizeOfCInt)
        elements.append(element)
        
        for normalIndex in 0...vertexCount-1 {
            normals[normalIndex] = SCNVector3Make(0, 0, -1)
        }
        sources.append(SCNGeometrySource(normals: normals))

        let uvData = NSData(bytes: uvList, length: uvList.count * sizeOfVecFloat)
        let uvSource = SCNGeometrySource(data: uvData as Data, semantic: SCNGeometrySource.Semantic.texcoord, vectorCount: uvList.count, usesFloatComponents: true, componentsPerVector: 2, bytesPerComponent: sizeOfFloat, dataOffset: 0, dataStride: sizeOfVecFloat)
        sources.append(uvSource)
        
        _terrainGeometry = SCNGeometry(sources: sources, elements: elements)
       
        let material = SCNMaterial()
        material.isLitPerPixel = true
        material.diffuse.magnificationFilter = .none
        material.diffuse.wrapS = .repeat
        material.diffuse.wrapT = .repeat
        material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(_terrainWidth*2), Float(_terrainLength*2), 1)
        material.diffuse.intensity = 1.0

        _terrainGeometry!.firstMaterial = material
        _terrainGeometry!.firstMaterial!.isDoubleSided = true
        
        return _terrainGeometry!
    }
    
    // -------------------------------------------------------------------------
    // MARK: - Create terrain
    
    func create(withImage image: UIImage?) {
        
        
        
        let terrainNode = SCNNode(geometry: createGeometry())
        
        self.addChildNode(terrainNode)
        if (image != nil) {
            self.texture = image
        }
        else {
            self.color = UIColor.green
        }
        
    }
    
    // -------------------------------------------------------------------------

    func create(withColor color: UIColor) {
        let terrainNode = SCNNode(geometry: createGeometry())
        self.addChildNode(terrainNode)
        
        self.color = color
    }

    // -------------------------------------------------------------------------
    // MARK: - Initialisation
    
    init(width: Int, length: Int, scale: Int) {
        super.init()
        
        _terrainWidth = width
        _terrainLength = length
        _heightScale = scale
    }

    // -------------------------------------------------------------------------

    required init(coder: NSCoder) {
        fatalError("Not yet implemented")
    }

    // -------------------------------------------------------------------------
}


0

There are 0 answers