Search code examples
iosswiftplot3dscenekit

Is there any way to invert/flip the result figure?


Using iOS - Swift Xcode 14 I have drawn an image using some points... and I got an image as output. But the image is an inverted or a flipped image. I used several code for flipping/Inverting the image but none of the code was working. by using of some codes the result image going out of the screen. The code provided here is 90% correct code but the image is flipped/inverted.

import SceneKit

class StairTwoDSceneViewController: UIViewController {

    var plotPointsArray: [[Double]] = [[0.0,0.0,0.0], [0.229,0.0,0.005], [0.229,-0.0249,0.1926], [0.4566,-0.0249,0.2073], [0.4341,-0.0315,0.3989],[0.6754,-0.0315,0.4039],[0.6513,-0.0315,0.5892], [0.8782,-0.0315,0.5971], [0.8541,-0.0315,0.7834],[1.0765,-0.0315,0.78781]]
    let lineWidth: CGFloat = 0.1 // Adjust this value to increase or decrease line thickness
    let cornerRadius: CGFloat = 0.02 // Adjust this value to control the size of the corner circles

    override func viewDidLoad() {
        super.viewDidLoad()
        let sceneView = SCNView(frame: view.bounds)
        sceneView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(sceneView)

        let scene = SCNScene()

        if plotPointsArray.count > 0 {
            let pointsNode = SCNNode()

            for (index, point) in plotPointsArray.enumerated() {
                guard point.count == 2 || point.count == 3 else {
                    continue // Skip invalid points
                }

                // Adjust the y-coordinate to place the green sphere at the left bottom
                let spherePosition = SCNVector3(
                    x: Float(point[0]),
                    y: Float(point.count == 2 ? -0.0315 : -point[1]), // Flip and adjust y-coordinate
                    z: Float(point[point.count - 1])
                )
                let sphereNode = drawSphere(at: spherePosition, radius: cornerRadius, color: index == 0 ? UIColor.green : UIColor.red)
                pointsNode.addChildNode(sphereNode)
            }

            scene.rootNode.addChildNode(pointsNode)

            for i in 0..<plotPointsArray.count - 1 {
                let currentPoint = plotPointsArray[i]
                let nextPoint = plotPointsArray[i + 1]

                guard currentPoint.count == nextPoint.count else {
                    continue // Skip invalid points
                }

                let start = SCNVector3(currentPoint[0], currentPoint.count == 2 ? -0.0315 : -currentPoint[1], currentPoint[currentPoint.count - 1])
                let end = SCNVector3(nextPoint[0], nextPoint.count == 2 ? -0.0315 : -nextPoint[1], nextPoint[nextPoint.count - 1])

                let lineGeometry = SCNGeometry.lineGeometry(from: start, to: end)
                let lineNode = SCNNode(geometry: lineGeometry)
                scene.rootNode.addChildNode(lineNode)
            }
        }

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.eulerAngles.x = -Float.pi / 2
        cameraNode.eulerAngles.y = 0
        cameraNode.position = SCNVector3(0.5, 2.5, 0.5)
        scene.rootNode.addChildNode(cameraNode)

        sceneView.scene = scene
        sceneView.backgroundColor = UIColor.black
        sceneView.allowsCameraControl = true
    }



    func drawLine(from start: SCNVector3, to end: SCNVector3, thickness: CGFloat, color: UIColor) -> SCNNode {
        let lineGeometry = SCNGeometry.lineGeometry(from: start, to: end)
        lineGeometry.firstMaterial?.diffuse.contents = color

        let lineNode = SCNNode(geometry: lineGeometry)
        lineNode.position = SCNVector3((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2)
        lineNode.eulerAngles.z = -atan2(end.x - start.x, end.y - start.y)

        return lineNode
    }

    func drawSphere(at position: SCNVector3, radius: CGFloat, color: UIColor) -> SCNNode {
        let sphereGeometry = SCNSphere(radius: radius)
        let sphereNode = SCNNode(geometry: sphereGeometry)
        sphereNode.position = position
        sphereNode.geometry?.firstMaterial?.diffuse.contents = color
        return sphereNode
    }
}

extension SCNGeometry {
    static func lineGeometry(from start: SCNVector3, to end: SCNVector3) -> SCNGeometry {
        let indices: [Int32] = [0, 1]

        let source = SCNGeometrySource(vertices: [start, end])
        let element = SCNGeometryElement(indices: indices, primitiveType: .line)

        return SCNGeometry(sources: [source], elements: [element])
    }
}
extension UIImage {
    func rotate(degrees: CGFloat) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        context.rotate(by: degrees * CGFloat.pi / 180)
        self.draw(in: CGRect(origin: .zero, size: self.size))
        let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return rotatedImage
    }
}

Output Image Based on given code

Actually I want the Inverted-image like this below The Output i need

Note : Dont change the points, the points getting from backend. I have used sample points here

I need to flip/Invert the image.


Solution

  • With the "stair steps" you have generated, the camera is looking from the Y-axis ... the "stairs" are descending on the Z-axis.

    Depending on what you want to do, you can either flip the camera:

        cameraNode.eulerAngles.x = -Float.pi / 2
        //cameraNode.eulerAngles.y = 0
        cameraNode.eulerAngles.y = -Float.pi / 2
    

    Or, translate the Z coordinates to invert the stairs:

        if plotPointsArray.count > 0 {
            let pointsNode = SCNNode()
    
            // only use elements that have x,y,z values
            plotPointsArray.removeAll(where: {$0.count != 3})
    
            // will never fail (because we're only here if .count > 0)
            //  but safely unwrap anyway
            guard let lastPoint = plotPointsArray.last
            else { return }
            
            // assuming for now the z-values are from 0.0 to a positive value
            let maxZ = lastPoint[2]
            
            for (index, point) in plotPointsArray.enumerated() {
                
                // Adjust the Z-coordinate to place the green sphere at the left bottom
                let spherePosition = SCNVector3(
                    x: Float(point[0]),
                    y: Float(point[1]),
                    z: Float(maxZ - point[2])
                )
    
                let sphereNode = drawSphere(at: spherePosition, radius: cornerRadius, color: index == 0 ? UIColor.green : UIColor.red)
                pointsNode.addChildNode(sphereNode)
            }
            
            scene.rootNode.addChildNode(pointsNode)
            
            // let's use those sphere nodes to generate the
            //  "connecting lines" instead of re-converting the plotPointsArray
            var startVect: SCNVector3 = pointsNode.childNodes[0].position
            
            for (i, n) in pointsNode.childNodes.enumerated() {
                
                // skip the first time through
                //  since we have only the starting point
                guard i != 0 else {
                    continue
                }
                
                let nextVect = n.position
                
                let lineGeometry = SCNGeometry.lineGeometry(from: startVect, to: nextVect)
                let lineNode = SCNNode(geometry: lineGeometry)
                
                scene.rootNode.addChildNode(lineNode)
                
                startVect = nextVect
            }
    
        }
    

    Alternatively, if we add the "spheres" and the "connector lines" to pointsNode, we could "invert" the entire node by scaling it and then moving it down (so the last sphere will be at Zero on the Z-axis):

            if plotPointsArray.count > 0 {
                let pointsNode = SCNNode()
                
                // only use elements that have x,y,z values
                plotPointsArray.removeAll(where: {$0.count != 3})
                
                // will never fail (because we're only here if .count > 0)
                //  but safely unwrap anyway
                guard let lastPoint = plotPointsArray.last
                else { return }
                
                // assuming for now the z-values are from 0.0 to a positive value
                let maxZ = lastPoint[2]
                
                var startVect: SCNVector3!
                
                for (index, point) in plotPointsArray.enumerated() {
                    
                    // Adjust the Z-coordinate to place the green sphere at the left bottom
                    let spherePosition = SCNVector3(
                        x: Float(point[0]),
                        y: Float(point[1]),
                        z: Float(point[2])
                    )
                    
                    let sphereNode = drawSphere(at: spherePosition, radius: cornerRadius, color: index == 0 ? UIColor.green : UIColor.red)
                    pointsNode.addChildNode(sphereNode)
                    
                    // add the "sphere connector lines" so they are
                    //  part of the points node and will rotate/move along with it
                    let nextVect = spherePosition
                    if let sv = startVect {
                        let lineGeometry = SCNGeometry.lineGeometry(from: startVect, to: nextVect)
                        let lineNode = SCNNode(geometry: lineGeometry)
                        pointsNode.addChildNode(lineNode)
                    }
                    startVect = nextVect
                    
                }
                
                let (minVec, maxVec) = pointsNode.boundingBox
                
                // scale on the z-axis
                //  and move by the height of pointsNode minus the sphere radius
                //  so the LAST point (the center of the LAST sphere) will be at z: 0.0
                var m = SCNMatrix4MakeScale(1.0, 1.0, -1.0)
                m = SCNMatrix4Translate(m, 0.0, 0.0, maxVec.z - Float(cornerRadius))
                pointsNode.pivot = m
                
                scene.rootNode.addChildNode(pointsNode)
                
            }
    

    with either approach, this is the result (I added axis lines for clarity - x-axis: red, y-axis: green, z-axis: blue):

    enter image description here