1

My idea is that i want to place 2 sphere nodes at selected locations. From that point i basically want to draw a rectangle that will adjust the height with a slider. Basically that means that the 2 spheres will represent all 4 corners in the beginning. But when testing the code i use 1 meter height as a test.

The problem i have is that i cant seem to place the rectangle at the correct location as illustrated in the image below:

image

the rectangle in the image has a higher y-point than the points, it's rotated slightly and its center point is above node 2 and not in between the nodes. I don't want to use node.rotation as it wont work dynamically.

This is the code i use to place the 2 nodes and draw + add the rectangle.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let location =  touches.first?.location(in: sceneView) else {return}
        let hitTest = sceneView.hitTest(location, types: [ARHitTestResult.ResultType.featurePoint])
        guard let result = hitTest.last else {return}

        // Converts the matrix_float4x4 to an SCNMatrix4 to be used with SceneKit
        let transform = SCNMatrix4.init(result.worldTransform)

        // Creates an SCNVector3 with certain indexes in the matrix
        let vector = SCNVector3Make(transform.m41, transform.m42, transform.m43)
        let node = addSphere(withPosition: vector)
        nodeArray.append(node)
        sceneView.scene.rootNode.addChildNode(node)

        if nodeArray.count == 2 {
            let node1 = sceneView.scene.rootNode.childNodes[0]
            let node2 = sceneView.scene.rootNode.childNodes[1]

            let bezeierPath = UIBezierPath()
            bezeierPath.lineWidth = 0.01
            bezeierPath.move(to: CGPoint(x: CGFloat(node1.position.x), y: CGFloat(node1.position.y)))
            bezeierPath.addLine(to: CGPoint(x: CGFloat(node2.position.x), y: CGFloat(node2.position.y)))
            bezeierPath.addLine(to: CGPoint(x: CGFloat(node2.position.x), y: CGFloat(node2.position.y+1.0)))
            bezeierPath.addLine(to: CGPoint(x: CGFloat(node1.position.x), y: CGFloat(node1.position.y+1.0)))
            bezeierPath.close()
            bezeierPath.fill()
            let shape = SCNShape(path: bezeierPath, extrusionDepth: 0.02)
            shape.firstMaterial?.diffuse.contents = UIColor.red.withAlphaComponent(0.8)
            let node = SCNNode.init(geometry: shape)
            node.position = SCNVector3(CGFloat(abs(node1.position.x-node2.position.x)/2), CGFloat(abs((node1.position.y)-(node2.position.y))/2), CGFloat(node1.position.z))
            sceneView.scene.rootNode.addChildNode(node)
        }

    }

Also note that this is not my final code. It will be refactored once i get everything working :).

Vollan
  • 1,887
  • 11
  • 26
  • Have you ever found a solution to this problem? I'm having a similar issue with this kind of implementation. – Waruna Aug 23 '18 at 05:25
  • I posted my current solution to it. altho i have refactored a lot of code. Hope it will help – Vollan Aug 23 '18 at 06:57

1 Answers1

0

This is currently working. I used the wrong calculation to set it in the middle of the 2 points.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Check if a window is placed already and fetch location in sceneView
    guard debugMode, let location = touches.first?.location(in: sceneView) else {return}
    // Fetch targets at the current location
    let hitTest = sceneView.hitTest(location, types: [ARHitTestResult.ResultType.featurePoint])
    guard let result = hitTest.last else {return}

    // Create sphere are position. getPosition() is an extension
    createSphere(withPosition: result.getPosition())

    // When 2 nodes has been placed, create a window and hide the spheres
    if nodeArray.count == 2 {
        let firstNode = nodeArray[nodeArray.count-1]
        let secondNode = nodeArray[nodeArray.count-2]
        firstNode.isHidden = true
        secondNode.isHidden = true
        createWindow(firstNode: firstNode, secondNode: secondNode)
        sceneView.debugOptions = []
    }
}

func createSphere(withPosition position: SCNVector3) {
    // Create an geometry which you will apply materials to and convert to a node
    let object = SCNSphere(radius: 0.01)
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.red
    object.materials = [material]
    let node = SCNNode(geometry: object)
    node.position = position
    nodeArray.append(node)
    sceneView.scene.rootNode.addChildNode(node)
}

func createWindow(firstNode: SCNNode, secondNode: SCNNode) {
    // Create an geometry which you will apply materials to and convert to a node
    let shape = SCNBox(width: CGFloat(abs(firstNode.worldPosition.x-secondNode.worldPosition.x)), height: 0.01, length: 0.02, chamferRadius: 0)
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.red.withAlphaComponent(0.8)
    // Each side of a cube can have different materials
    // https://stackoverflow.com/questions/27509092/scnbox-different-colour-or-texture-on-each-face
    shape.materials = [material]
    let node = SCNNode(geometry: shape)
    let minX = min(firstNode.worldPosition.x, secondNode.worldPosition.x)
    let maxX = max(firstNode.worldPosition.x, secondNode.worldPosition.x)

    // The nodes pivot Y-axis should be split in half of the nodes height, this will cause the node to
    // only scale in one direction, when scaling it
    // https://stackoverflow.com/questions/42568420/scenekit-understanding-the-pivot-property-of-scnnode/42613002#42613002
    node.position = SCNVector3(((maxX-minX)/2)+minX, firstNode.worldPosition.y, firstNode.worldPosition.z)
    node.pivot = SCNMatrix4MakeTranslation(0, 0.005, 0)
    node.scale = SCNVector3Make(1, -50, 1)
    node.name = "window"

    sceneView.scene.rootNode.addChildNode(node)
}

Also added:

 @objc func resetSceneView() {
    // Clear all the nodes
    sceneView.scene.rootNode.enumerateHierarchy { (node, stop) in
        node.childNodes.forEach({
            $0.removeFromParentNode()
        })
        node.removeFromParentNode()
    }

    // Reset the session
    sceneView.session.pause()
    sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
    sceneView.session.run(configuration, options: .resetTracking)
    let matrix = sceneView.session.currentFrame!.camera.transform
    sceneView.session.setWorldOrigin(relativeTransform: matrix)
    nodeArray = []
}

To make sure that the world alignment is reset to your camera position

Vollan
  • 1,887
  • 11
  • 26
  • How would you do this differently if you have a draw a filled polygon connecting multiple points. – Waruna Aug 23 '18 at 08:32
  • @Waruna Then I would use `UIBezeierPath` to draw it between the points assigned. Then I would calculate the middle of the polygon: https://www.mathopenref.com/polygonradius.html and then apply it as the position – Vollan Aug 23 '18 at 09:33