14

I am developing ARKit Application using 3d models. So for that I have used 3d models & added gestures for move, rotate & zoom 3d models.

Now I am facing only 1 issue but I am not sure if this issue relates to what. Is there an issue in 3d model or if anything missing in my program.

Issue is the 3d model I am using shows very big & goes out of the screen. I am trying to scale it down size but its very big.

Here is my code :

@IBOutlet var mySceneView: ARSCNView!
var selectedNode = SCNNode()
var prevLoc = CGPoint()
var touchCount : Int = 0

override func viewDidLoad() {
    super.viewDidLoad()
    self.lblTitle.text = self.sceneTitle
    let mySCN = SCNScene.init(named: "art.scnassets/\(self.sceneImagename).scn")!
    self.mySceneView.scene = mySCN

    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3Make(0, 0, 0)
    self.mySceneView.scene.rootNode.addChildNode(cameraNode)
    self.mySceneView.allowsCameraControl = true
    self.mySceneView.autoenablesDefaultLighting = true

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(detailPage.doHandleTap(_:)))
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(detailPage.doHandlePan(_:)))
    let gesturesArray = NSMutableArray()
    gesturesArray.add(tapGesture)
    gesturesArray.add(panGesture)
    gesturesArray.addObjects(from: self.mySceneView.gestureRecognizers!)
    self.mySceneView.gestureRecognizers = (gesturesArray as! [UIGestureRecognizer])
}

//MARK:- Handle Gesture
@objc func doHandlePan(_ sender: UIPanGestureRecognizer) {
    var delta = sender.translation(in: self.view)
    let loc = sender.location(in: self.view)
    if sender.state == .began {
        self.prevLoc = loc
        self.touchCount = sender.numberOfTouches
    } else if sender.state == .changed {
        delta = CGPoint(x: loc.x - prevLoc.x, y: loc.y - prevLoc.y)
        prevLoc = loc
        if self.touchCount != sender.numberOfTouches {
            return
        }

        var rotMat = SCNMatrix4()
        if touchCount == 2 {
            rotMat = SCNMatrix4MakeTranslation(Float(delta.x * 0.025), Float(delta.y * -0.025), 0)
        } else {
            let rotMatX = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.y), 1, 0, 0)
            let rotMatY = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.x), 0, 1, 0)
            rotMat = SCNMatrix4Mult(rotMatX, rotMatY)
        }

        let transMat = SCNMatrix4MakeTranslation(selectedNode.position.x, selectedNode.position.y, selectedNode.position.z)
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(transMat))

        let parentNodeTransMat = SCNMatrix4MakeTranslation((selectedNode.parent?.worldPosition.x)!, (selectedNode.parent?.worldPosition.y)!, (selectedNode.parent?.worldPosition.z)!)
        let parentNodeMatWOTrans = SCNMatrix4Mult(selectedNode.parent!.worldTransform, SCNMatrix4Invert(parentNodeTransMat))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, parentNodeMatWOTrans)

        let camorbitNodeTransMat = SCNMatrix4MakeTranslation((self.mySceneView.pointOfView?.worldPosition.x)!, (self.mySceneView.pointOfView?.worldPosition.y)!, (self.mySceneView.pointOfView?.worldPosition.z)!)
        let camorbitNodeMatWOTrans = SCNMatrix4Mult(self.mySceneView.pointOfView!.worldTransform, SCNMatrix4Invert(camorbitNodeTransMat))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(camorbitNodeMatWOTrans))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat)

        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, camorbitNodeMatWOTrans)
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(parentNodeMatWOTrans))
        selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, transMat)
    }
}

@objc func doHandleTap(_ sender: UITapGestureRecognizer) {
    let p = sender.location(in: self.mySceneView)
    var hitResults = self.mySceneView.hitTest(p, options: nil)

    if (p.x > self.mySceneView.frame.size.width-100 || p.y < 100) {
        self.mySceneView.allowsCameraControl = !self.mySceneView.allowsCameraControl
    }

    if hitResults.count > 0 {
        let result = hitResults[0]
        let material = result.node.geometry?.firstMaterial
        selectedNode = result.node

        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.3

        SCNTransaction.completionBlock = {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.3
            SCNTransaction.commit()
        }
        material?.emission.contents = UIColor.white
        SCNTransaction.commit()
    }
}

My Question is :

Can we set any size of 3d object model Aspect fit in screen size in the centre of the screen ? Please suggest if there is some way for it.

Any guidence or suggestions will be highly appreciated.

Mayur Prajapati
  • 5,454
  • 7
  • 41
  • 70

2 Answers2

8

What you need to is to use getBoundingSphereCenter to get the bounding sphere size, then can project that to the screen. Or alternatively get the ratio of that radius over the distance between scenekit camera and the object position. This way you will know how big the object will look on the screen. To the scale down, you simple set the scale property of your object.

For the second part, you can use projectPoint.

alex papa
  • 616
  • 1
  • 4
  • 10
  • can you please elaborate your answer so I can get proper solution & can reward you bounty. I appreciate your answer but I still need it with proper solution not just theoritical because I am in learning stage so I have not much idea. – Mayur Prajapati Jul 22 '19 at 04:33
  • @Mayur I suggest you break the problem into two parts. First learn how to use `getBoundingSphereCenter` once you have that you need to figure out how to write the code for project that to the screen. If you have some code already written for this it's more likely someone will help you rather than implementing the whole thing. Does that make sense? – alex papa Jul 22 '19 at 04:46
  • @Mayur I will update the answer with more help in a bit. – alex papa Jul 22 '19 at 04:47
  • if possible can you please join my room here : https://chat.stackoverflow.com/rooms/99405/ios-android-react-native-apps – Mayur Prajapati Jul 22 '19 at 04:53
  • any proper solution ? I still didn't get this proper – Mayur Prajapati Jul 23 '19 at 06:25
2

The way I handled this is making sure the 3D model always has a fixed size.

For example, if the 3D model is a small cup or a large house, I insure it always has a width of 25 cm on the scene's coordinate space (while maintaining the ratios between x y z).

You can calculate the width of the bounding box of the node like this:

let mySCN = SCNScene(named: "art.scnassets/\(self.sceneImagename).scn")!

let minX = mySCN.rootNode.boundingBox.min.x
let maxX = mySCN.rootNode.boundingBox.max.x

// change 0.25 to whatever you need
// this value is in meters
let scaleValue = 0.25 / abs(minX - maxX)

// scale all axes of the node using `scaleValue`
// this maintains ratios and does not stretch the model
mySCN.rootNode.scale = SCNVector3(scaleValue, scaleValue, scaleValue)

self.mySceneView.scene = mySCN

You can also calculate the scale value based on height or depth by using the y or z value of the bounding box.

alobaili
  • 761
  • 9
  • 23