(iOS SceneKit) Convert texture coordinate to xyz coordinate - swift

I'm able to get the SCNHitTestResult with a long press gesture using the codes below, so that I can get the worldCoordinates(x,y,z) and textureCoordinates.
guard recognizer.state != .ended else { return }
let point = recognizer.location(in: self.panoramaView.sceneView)
guard let hitTest = self.panoramaView.sceneView.hitTest(point, options: nil).first else { return }
let textureCoordinates = hitTest.textureCoordinates(withMappingChannel: 0)
Now the problem is, with a given textureCoordinates, is it possible to convert it to worldCoordinates?
ps: I'm using CTPanoramaView library.

Related

How to get a tap position in ARKit?

I am trying to tap the screen and get tap position then I can fire the bullet from the position I tap.
I searched and tried many ways such as below but it doesn't work
guard let pointOfView = sceneView.pointOfView else { return }
let transform = pointOfView.transform
let orientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33)
let scnView = sender.view as! ARSCNView
let holdLocation = sender.location(in: scnView)
let estimatedPlane: ARRaycastQuery.Target = .existingPlaneInfinite
let alignment: ARRaycastQuery.TargetAlignment = .any
guard let query = scnView.raycastQuery(from: holdLocation,
allowing: estimatedPlane,
alignment: alignment)
else { return }
let results = scnView.session.raycast(query)
guard let pointResult = results.first else { return }
let pointTransform = SCNMatrix4(pointResult.worldTransform)
let pointLocation = SCNVector3(pointTransform.m41,
pointTransform.m42,
pointTransform.m43)
let pointPosition = orientation + pointLocation
Can anyone help to tell me how to do it? Highly appreciate it.

Node name was overwritten by USDZ model

I'm currently developing an iOS app that uses ARKit and SceneKit for the augmented reality.
I'm having a problem while loading an .usdz model into the scene.
I can load it correctly into the scene, but, when I try to get the node name (in which I loaded the .usdz model) after tapping on it, it returns the .usdz name and not the name I gave to it.
The code I use to load the .usdz model is:
let mdlAsset = MDLAsset(url: urlPath)
mdlAsset.loadTextures()
let asset = mdlAsset.object(at: 0) // extract first object
var assetNode = SCNNode(mdlObject: asset)
assetNode = SCNNode(mdlObject: asset)
assetNode.name = "Node-2"
sceneView.scene.rootNode.addChildNode(assetNode)
To capture the tap on the node, the code is:
#objc func handleTap(recognizer: UITapGestureRecognizer){
let location = recognizer.location(in: sceneView)
let results = sceneView.hitTest(location, options: nil)
guard recognizer.state == .ended else { return }
if results.count > 0 {
let result = results[0] as SCNHitTestResult
let node = result.node
print(node.name)
}
}
As I mentioned before, when I tap on the object it prints
Optional("Sphere_0")
value that can be found in the top right corner, in the model details page.
The correct value that I expected was "Node-2".
The name of your USDZ model isn't overridden. It's the peculiarities of SceneKit hit-testing and scene hierarchy. When you perform a hit-test search, SceneKit looks for SCNGeometry objects (not a main node) along the ray you specify. So, all you need to do, once the hit-test is completed, is to find the corresponding parent nodes.
Try this code:
import SceneKit.ModelIO
class GameViewController: UIViewController {
var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView = (self.view as! SCNView)
let scene = SCNScene(named: "art.scnassets/ship.scn")!
sceneView.scene = SCNScene()
sceneView.backgroundColor = .black
let recog = UITapGestureRecognizer(target: self, action: #selector(tap))
sceneView.addGestureRecognizer(recog)
// ASSET
let mdlAsset = MDLAsset(scnScene: scene)
let asset = mdlAsset.object(at: 0)
let node = SCNNode(mdlObject: asset.children[0])
node.name = "Main-Node-Name" // former "ship"
node.childNodes[0].name = "SubNode-Name" // former "shipMesh"
node.childNodes[0].childNodes[0].name = "Geo-Name" // former "Scrap_MeshShape"
sceneView.scene?.rootNode.addChildNode(node)
}
}
And your hit-testing method:
extension GameViewController {
#objc func tap(recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: sceneView)
let results = sceneView.hitTest(location)
guard recognizer.state == .ended else { return }
if results.count > 0 {
let result = results[0] as SCNHitTestResult
let node = result.node
print(node.name!) // Geo-Name
print(node.parent!.name!) // SubNode-Name
print(node.parent!.parent!.name!) // Main-Node-Name
}
}
}

Remove complete .scn instead of individual nodes Swift ARKit

I am attempting to delete the .scn objects I placed down. However, with my current code, it is just deleting individual nodes. Here is how I handle the tap delete.
#objc func Erase(sender: UITapGestureRecognizer){
print("rendering")
//sharedVM.count = sharedVM.count + 1
guard let pointOfView = sceneView.pointOfView else {return}
guard let cameraPosition = getCameraPosition(in: sceneView) else {
return
}
let location = sender.location(in: view)
let currentPositionOfCamera = cameraPosition + getRay(for: location, in: sceneView)
DispatchQueue.main.async{
//guard let location = touches.first?.location(in: sceneView) else { return }
let results = self.sceneView.hitTest(location, options: [SCNHitTestOption.searchMode : 1])
for result in results { /// See if the beam hit the cube
let Node = result.node
Node.enumerateChildNodes { (node, stop) in
node.removeFromParentNode() }
Node.removeFromParentNode()
}
}
}
Here is how I place the object:
var objecttest = VirtualObject(url: referenceURL)!
//var objecttest = VirtualObject(url: URL(string: "Models.scnassets/cup/cup.scn")!)
objecttest.load()
self.sceneView.scene.rootNode.addChildNode(objecttest)
class VirtualObject: SCNReferenceNode {
...
}

How to Zoom .DAE 3D model using ARKit iOS

I have a 3D model with .scn extension. How to zoom it with pinch gesture without virtualobject file from iOS sample Placing Objects application.
Pinch gesture works well with .scn's if its converted from .obj file. But its not working with .dae model.
func addPinchGestureToSceneView() {
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(scale))
pinchGesture.scale = 1.0;
pinchGesture.delegate = self
self.sceneView.addGestureRecognizer(pinchGesture)
}
private func node(at position: CGPoint) -> SCNNode? {
var hitTestOptions = [SCNHitTestOption: Any]()
hitTestOptions[SCNHitTestOption.boundingBoxOnly] = true
return sceneView.hitTest(position, options: hitTestOptions)
.first(where: { self.getOnlyModelName(name: $0.node.name ?? "") == self.currentmodel.modelname})?
.node
}
#objc func scale(_ gesture: UIPinchGestureRecognizer) {
if self.currentmodel.isZoomEnabled == false{
return
}
let location = gesture.location(in: sceneView)
guard let node = node(at: location)else{return}
// guard let node = node(at: location) else { return }
switch gesture.state {
case .began:
originalScale = node.scale
gesture.scale = CGFloat(node.scale.x)
print("Begin:: \(originalScale)")
case .changed:
guard var originalScale = originalScale else { return }
if gesture.scale > 2.0{
return
}
originalScale.x = Float(gesture.scale)
originalScale.y = Float(gesture.scale)
originalScale.z = Float(gesture.scale)
node.scale = originalScale
case .ended:
guard var originalScale = originalScale else { return }
if gesture.scale > 2.0{
return
}
originalScale.x = Float(gesture.scale)
originalScale.y = Float(gesture.scale)
originalScale.z = Float(gesture.scale)
node.scale = originalScale
gesture.scale = CGFloat(node.scale.x)
default:
gesture.scale = 1.0
originalScale = nil
}
When it's a dae, you might need to try to grab the parent of the node caught in the hit test. I had a similar issue with dae that got solved by getting the parent of the child, or even sometimes the grandparent of the child.

Calculate each distance from user to several points by route in Swift

I'm a newbie in swift and I'm trying to calculate the distance, on route, from userLocation to several Point of interest.
I don’t want, in this part of the app, “draw” the route on a map, but I only want to calculate the distance on route instead the distance between two coordinate and then show this distance as information inside the callout on the map.
The coordinate of the POI (latitude and longitude) are contained in a database.
I have read some threads about this argument here on Stack Overflow:
Measuring distance in meters from a drawn route in MKMapView
MKMapView get distance between coordinates on customized route
and other tutorials:
http://www.technetexperts.com/mobile/draw-route-between-2-points-on-map-with-ios7-mapkit-api/
https://videos.raywenderlich.com/courses/mapkit-and-core-location/lessons/9
then i wrote this code:
for item in items {
if item.latitudine != "" && item.longitudine != "" {
// Point Of Interest coordinate
let latitude = Double(item.latitude!)
let longitude = Double(item.longitude!)
let itemLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let itemLocationPlacemark = MKPlacemark(coordinate: itemLocation.coordinate, addressDictionary: nil)
// user coordinate
let userLocation = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let userLocationPlacemark = MKPlacemark(coordinate: userLocation.coordinate, addressDictionary: nil)
// create Request object
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: userLocationPlacemark)
request.destination = MKMapItem(placemark: itemLocationPlacemark)
request.requestsAlternateRoutes = false
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate {
[weak self] (response, error) in
if error == nil {
for route in (response?.routes)! {
let distance = (route.distance)/1000
print(distance)
}
}
}
}
}
The problem is when I execute the code from the line directions.calculate.
The program run the line but then don’t execute the rest, don’t execute the control if error == nil and the instructions in the closure.
So now I wonder if my idea is wrong and, if not, how can obtain my goal.
(Posted solution on behalf of the OP).
Reading other threads I understand that the problem was the closure inside che for loop. So after several attempts I found the solution that work for me. I write it here so that can be useful to someone else:
var counter: Int!
...
for item in itemPin {
if item.latitude != "" && item.longitude != "" {
....
....
let directions = MKDirections(request: request)
directions.calculate {
(response, error) in
print("error \(error)")
if error == nil {
counter = self.counter + 1
print(self.counter)
if self.counter >= self.itemPin.count {
let result = (response?.routes[0].distance)!/1000
}
}
}
...
}
}