0

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

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


Damiano Miazzi
  • 1,514
  • 1
  • 16
  • 38
  • Is your final Terrain Node of Type "RBTerrain" or of Type "SCNNode" - this could be the Issue when unarchiving, if the type is RBTerrain, which is a kind of extended/modifyed SCNNode. You could probably try to unarchive like so: NSKeyedUnarchiver.unarchivedObject(ofClass: RBTerrain.self, from: data) - and the object to which you assign the data should then also be of type "RBTerrain" (like: var terrainNode : RBTerrain!) – ZAY Apr 01 '22 at 15:43
  • thanks for the advise but for some reason even if use of type RBTerrain does't work. still gave me same issue.I managed to solve only the geometry of type SCNGeometry. But I can't find the issue why I can't save this in of type RBTerrain – Damiano Miazzi Apr 02 '22 at 13:18
  • I am sorry for the inconvenience, I currently have no idea why it does not work as expected. – ZAY Apr 02 '22 at 17:38

0 Answers0