Draw straight line from start value to end value ARKit - ios

As always I need your help. I need to draw a straight line from a start value (declared as SCNVector3) and connected to the position in the real world till the end point.
Can someone explain to me with some lines of code how can I do it?
Thank you!

You'll need to implement:
touchesBegan, touchesMoved, touchesEnded, touchesCancelled
for your camera view controller.
In touchesBegan you will need to make hitTest for current ARFrame in the UITouch location. Then you'll have your startPosition and your lastTouch, which is your start UITouch.
Then you will need to add timer with 0.016_667 interval (60Hz) which will update your last touch position with hitTest when you camera moves. Same you'll do in touchesMoved function. And in touchesMoved you'll also update your lastTouch. So at this point you'll have your startPosition and currentPosition, which is SCNVector3. And you can just redraw SCNCylinder (with 0.001m radius for example) for those positions if you need straight line.
At last step in touchesEnded you'll fix your line, or if touchesCancelled you will remove it and clear lastTouch.
UPD
If you need 2D line on the screen, then you'll need to use projectPoint for your ARSceneView.
3D line drawing
For drawing 3D line you could SCNGeometry use extension:
extension SCNGeometry {
class func line(from vector1: SCNVector3, to vector2: SCNVector3) -> SCNGeometry {
let indices: [Int32] = [0, 1]
let source = SCNGeometrySource(vertices: [vector1, vector2])
let element = SCNGeometryElement(indices: indices, primitiveType: .line)
return SCNGeometry(sources: [source], elements: [element])
}
}
Using:
let line = SCNGeometry.line(from: startPosition, to: endPosition)
let lineNode = SCNNode(geometry: line)
lineNode.position = SCNVector3Zero
sceneView.scene.rootNode.addChildNode(lineNode)

Related

Swift 4 and SpriteKit: Move node to a new touch location

I'm trying to move a node to a touch location using physicsBody (as opposed to SKAction). I am trying to use applyForce, but I may be going about this wrong.
In my touchesBegan function I record the touch location in a constant and pass it into my movePlayer function. The initial touch works as expected. However, subsequent touches seem to move from a relative position, which makes me think that I should recalculate my vector. I'm just not sure how to do this. Any help is appreciated.
func movePlayer(to touchPoint: CGPoint) {
let vector = CGVector(dx: touchPoint.x, dy: touchPoint.y)
player.physicsBody?.applyForce(vector, at: player.position)
player.physicsBody?.velocity = vector
playerIsMoving = true
}

Get vector in SCNNode environment from touch location swift

I have the position and orientation of my camera, the CGPoint touch location on the screen, I need the line (preferably vector) in the direction that I touched on the screen in my 3d SCNNode environment, how can I get this?
A code snippet would be very helpful.
You can use the SCNSceneRenderer.unprojectPoint(_:) method for this.
This method, which is implemented by SCNView, takes the coordinates of your point as a SCNVector3. Set the first two elements in the coordinate space of your view. Apple describes the use of the third element:
The z-coordinate of the point parameter describes the depth at which to unproject the point relative to the near and far clipping planes of the renderer’s viewing frustum (defined by its
pointOfView
node). Unprojecting a point whose z-coordinate is 0.0 returns a point on the near clipping plane; unprojecting a point whose z-coordinate is 1.0 returns a point on the far clipping plane.
You are not looking for the location of these points, but for the line that connects them. Just subtract both to get the line.
func getDirection(for point: CGPoint, in view: SCNView) -> SCNVector3 {
let farPoint = view.unprojectPoint(SCNVector3Make(point.x, point.y, 1))
let nearPoint = view.unprojectPoint(SCNVector3Make(point.x, point.y, 0))
return SCNVector3Make(farPoint.x - nearPoint.x, farPoint.y - nearPoint.y, farPoint.z - nearPoint.z)
}

Troubles to detect if a CGPoint is inside a square (diamond-shape)

I have 2 SKSpriteNode:
a simple square (A)
the same square with a rotation (-45°) (B)
I need to check, at any time, if the center of another SKSpriteNode (a ball) is inside one of these squares.
The ball and the squares have the same parent (the main scene).
override func update(_ currentTime: TimeInterval) {
let spriteArray = self.nodes(at: ball.position)
let arr = spriteArray.filter {$0.name == "square"}
for square in arr {
print(square.letter)
if(square.contains(self.puck.position)) {
print("INSIDE")
}
}
}
With the simple square (A), my code works correctly. The data are right. I know, at any time, if the CGPoint center is inside or outside the square.
But with the square with the rotation (B), the data aren't as desired. The CGPoint is detected inside as soon as it's in the square which the diamond-shape is contained.
The SKSpriteNode squares are created via the level editor.
How can I do to have the correct result for the diamond-shape?
EDIT 1
Using
view.showsPhysics = true
I can see the bounds of all the SKSpriteNode with physicsBody. The bounds of my diamond-square is the diamond-square and not the grey square area.
square.frame.size -> return the grey area
square.size -> return the diamond-square
In the Apple documentation, func nodes(at p: CGPoint) -> [SKNode], the method is about node and not frame, so why it doesn't work?
There are many ways to do it, usually I like to work with paths so , if you have a perfect diamond as you describe I would like to offer a different way from the comments, you could create a path that match perfectly to your diamond with UIBezierPath because it have the method containsPoint:
let f = square.frame
var diamondPath = UIBezierPath.init()
diamondPath.moveToPoint(CGPointMake(f.size.width-f.origin.x,f.origin.y))
diamondPath.addLineToPoint(CGPointMake(f.origin.x,f.size.height-f.origin.y))
diamondPath.addLineToPoint(CGPointMake(f.size.width-f.origin.x,f.size.height))
diamondPath.addLineToPoint(CGPointMake(f.size.width,f.size.height-f.origin.y))
diamondPath.closePath()
if diamondPath.containsPoint(<#T##point: CGPoint##CGPoint#>) {
// point is inside diamond
}

Move a specific node in SceneKit using touch

I have a scene with multiple nodes. I want to select a node by tapping it (if I tap nothing I want nothing to happen) and make him follow my finger only on XY axis (I know position on Z axis). Is there any method that converts location in view to SceneKit coords?
After few researches I found this and it's exactly what I want, but I don't get the code. Can somebody explain me or help me figure how can I solve my problem?
https://www.youtube.com/watch?v=xn9Bt2PFp0g
func CGPointToSCNVector3(view: SCNView, depth: Float, point: CGPoint) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3Make(0, 0, depth))
let locationWithz = SCNVector3Make(Float(point.x), Float(point.y), projectedOrigin.z)
return view.unprojectPoint(locationWithz)
}
Looks like was pretty simple, I've made a function that gets 3 parameters. View is the SCNView where scene is attached to, depth is the z value of node, and point is a CGPoint that represents projection of 3D scene on screen.
This is Alec Firtulescu's answer as an extension for watchOS (convert it to iOS by changing WKInterfaceSCNScene to SCNView):
extension CGPoint {
func scnVector3Value(view: WKInterfaceSCNScene, depth: Float) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3(0, 0, depth))
return view.unprojectPoint(SCNVector3(Float(x), Float(y), projectedOrigin.z))
}
}

SceneKit get texture coordinate after touch with Swift

I want to manipulate 2D textures in a 3D SceneKit scene.
Therefore i used this code to get local coordinates:
#IBAction func tap(sender: UITapGestureRecognizer) {
var arr:NSArray = my3dView.hitTest(sender.locationInView(my3dView), options: NSDictionary(dictionary: [SCNHitTestFirstFoundOnlyKey:true]))
var res:SCNHitTestResult = arr.firstObject as SCNHitTestResult
var vect:SCNVector3 = res.localCoordinates}
I have the texture read out from my scene with:
var mat:SCNNode = myscene.rootNode.childNodes[0] as SCNNode
var child:SCNNode = mat.childNodeWithName("ID12", recursively: false)
var geo:SCNMaterial = child.geometry.firstMaterial
var channel = geo.diffuse.mappingChannel
var textureimg:UIImage = geo.diffuse.contents as UIImage
and now i want to draw at the touchpoint to the texture...
how can i do that? how can i transform my coordinate from touch to the texture image?
Sounds like you have two problems. (Without even having used regular expressions. :))
First, you need to get the texture coordinates of the tapped point -- that is, the point in 2D texture space on the surface of the object. You've almost got that right already. SCNHitTestResult provides those with the textureCoordinatesWithMappingChannel method. (You're using localCoordinates, which gets you a point in the 3D space owned by the node in the hit-test result.) And you already seem to have found the business about mapping channels, so you know what to pass to that method.
Problem #2 is how to draw.
You're doing the right thing to get the material's contents as a UIImage. Once you've got that, you could look into drawing with UIGraphics and CGContext functions -- create an image with UIGraphicsBeginImageContext, draw the existing image into it, then draw whatever new content you want to add at the tapped point. After that, you can get the image you were drawing with UIGraphicsGetImageFromCurrentImageContext and set it as the new diffuse.contents of your material. However, that's probably not the best way -- you're schlepping a bunch of image data around on the CPU, and the code is a bit unwieldy, too.
A better approach might be to take advantage of the integration between SceneKit and SpriteKit. This way, all your 2D drawing is happening in the same GPU context as the 3D drawing -- and the code's a bit simpler.
You can set your material's diffuse.contents to a SpriteKit scene. (To use the UIImage you currently have for that texture, just stick it on an SKSpriteNode that fills the scene.) Once you have the texture coordinates, you can add a sprite to the scene at that point.
var nodeToDrawOn: SCNNode!
var skScene: SKScene!
func mySetup() { // or viewDidLoad, or wherever you do setup
// whatever else you're doing for setup, plus:
// 1. remember which node we want to draw on
nodeToDrawOn = myScene.rootNode.childNodeWithName("ID12", recursively: true)
// 2. set up that node's texture as a SpriteKit scene
let currentImage = nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents as UIImage
skScene = SKScene(size: currentImage.size)
nodeToDrawOn.geometry!.firstMaterial!.diffuse.contents = skScene
// 3. put the currentImage into a background sprite for the skScene
let background = SKSpriteNode(texture: SKTexture(image: currentImage))
background.position = CGPoint(x: skScene.frame.midX, y: skScene.frame.midY)
skScene.addChild(background)
}
#IBAction func tap(sender: UITapGestureRecognizer) {
let results = my3dView.hitTest(sender.locationInView(my3dView), options: [SCNHitTestFirstFoundOnlyKey: true]) as [SCNHitTestResult]
if let result = results.first {
if result.node === nodeToDrawOn {
// 1. get the texture coordinates
let channel = nodeToDrawOn.geometry!.firstMaterial!.diffuse.mappingChannel
let texcoord = result.textureCoordinatesWithMappingChannel(channel)
// 2. place a sprite there
let sprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 10, height: 10))
// scale coords: texcoords go 0.0-1.0, skScene space is is pixels
sprite.position.x = texcoord.x * skScene.size.width
sprite.position.y = texcoord.y * skScene.size.height
skScene.addChild(sprite)
}
}
}
For more details on the SpriteKit approach (in Objective-C) see the SceneKit State of the Union Demo from WWDC14. That shows a SpriteKit scene used as the texture map for a torus, with spheres of paint getting thrown at it -- whenever a sphere collides with the torus, it gets a SCNHitTestResult and uses its texcoords to create a paint splatter in the SpriteKit scene.
Finally, some Swift style comments on your code (unrelated to the question and answer):
Use let instead of var wherever you don't need to reassign a value, and the optimizer will make your code go faster.
Explicit type annotations (res: SCNHitTestResult) are rarely necessary.
Swift dictionaries are bridged to NSDictionary, so you can pass them directly to an API that takes NSDictionary.
Casting to a Swift typed array (hitTest(...) as [SCNHitTestResult]) saves you from having to cast the contents.

Resources