I am getting lost in how nodespace coordinates and rotation are handled in scenekit.
How do I get the direction a node is facing after rotation so I can then apply force in that direction.
I assumed that a force along the -z axis applied to the node would always move it forward relative to the node however this is not the case.
I also couldn't get convertPosition:toNode to work. Variations on ship.convertPosition(SCNVector3(0,0,-0.1), toNode: ship.parentNode) made the node fly off at unpredictable directions and speeds.
What worked for me, was to grab the third row of the node's worldTransform matrix, which corresponds to it's z-forward axis:
func getZForward(node: SCNNode) -> SCNVector3 {
return SCNVector3(node.worldTransform.m31, node.worldTransform.m32, node.worldTransform.m33)
}
ship.position += getZForward(ship) * speed
// if node has a physics body, you might need to use the presentationNode, eg:
// getZForward(ship.presentationNode)
// though you probably don't want to be directly modifying the position of a node with a physics body
// overrides for SCNVector arithmetic
#if os(iOS)
typealias VecFloat = Float
#elseif os (OSX)
typealias VecFloat = CGFloat
#endif
func + (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 {
return SCNVector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z)
}
func * (lhs: SCNVector3, rhs: VecFloat) -> SCNVector3 {
return SCNVector3(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs)
}
func += (lhs: inout SCNVector3, rhs: SCNVector3) {
lhs = lhs + rhs
}
If the node has a physics body you might have to pass the node.presentationNode into the function.
When iOS 11/ High Sierra come out, there'll be less need for overriding SCNVector3, because all the SCNNode properties have simd equivalents, so as well as .position there's .simdPosition and so on, and there are a lot of common simd operations built in.
iOS 11 update
iOS 11 adds handy convenience functions for getting the orientation of a node. In this case the worldForward property is the one you want. Also, all of the properties on SCNNode that return SCNVector and matrix types now have versions that return simd types. Because simd already has overloads for the arithmetic operators, you no longer need to add sets of arithmetic overrides for the SCNVector and SCNMatrix types.
So we can get rid of out getZForward method above, and just have the line:
ship.simdPosition += ship.simdWorldFront * speed
The other handy set of methods that iOS 11 adds, are a set of convertVector methods, to complement the existing convertPosition methods. convertVector is the equivalent of multiplying the matrix by the vector with 0 in the w position, so that the translation of the matrix is ignored. These are the appropriate methods to use for converting things like normals, directions and so on from one node's space to another.
Because the accepted answer uses convertPosition, I believe it will only produce correct results for nodes whose translation is the 0,0,0 origin
Negative z is the "facing" direction of a node only in its own coordinate space (i.e. the one its children and geometry are positioned in). When you apply a force, you're working in the coordinate space containing the node.
So, if you want to apply a force in a node's forward direction, you'll need to convert a vector like {0,0,-1} from the node's space to its parent's using a method like convertPosition:toNode:.
Related
I am completely new to IOS development and Swift. At present I am working on an IOS app that involves scanning a room using LiDAR sensor of IPad and later when I load the 3D Obj file and touch two arbitrary points, the length between two points should be displayed. Something similar to 3D scanner App, Canvas app.
So far I am able to export the mesh data in to an Obj file and save it to the device. I have tried for a while, but I think am kind of stuck at this point as I do not know how to proceed further for the measuring part.
The end result should look something like this.
an exported obj file with the distance label
Looking for any guidance/suggestions.
Scenekit uses meters, just fyi. You may have to experiment with scaling on this, I kind of doubt it will match out of the box. This assumes you have the nodes to compare distances, otherwise it's a different deal.
You can use GLKVector3Distance, or just roll your own:
func distance3D(vector1: SCNVector3, vector2: SCNVector3) -> Float
{
let x: Float = (vector1.x - vector2.x) * (vector1.x - vector2.x)
let y: Float = (vector1.y - vector2.y) * (vector1.y - vector2.y)
let z: Float = (vector1.z - vector2.z) * (vector1.z - vector2.z)
let temp = x + y + z
return Float(sqrtf(Float(temp)))
}
or:
extension SCNVector3 {
func distance(to vector: SCNVector3) -> Float {
return simd_distance(simd_float3(self), simd_float3(vector))
}
}
I have an issue with ARkit, I use ARWorldTrackingConfiguration and when I have a SCNNode one meter away from me, when I use Iphone X and move to the SCNNode, the Z position of my camera SCNNode updates and I know that I am closer to the node, but with other Iphones (Iphone 8) I don't get an update to Z position.
Also with GPS even with kCLLocationAccuracyBestForNavigation I don't have accuracy.
How can I know that I am closer to the SCNNode?? Thank you
implementing ARSCNViewDelegate in your ViewController, it will be called the renderer:updateAtTime callback once per frame.
Inside this function you can get the position of your camera relative to the real world calling sceneView.session.currentFrame.camera.transform which is a simd_float4x4. You can access the current position of your camera with transform.columns.3 and peeking the x,y,z field from it. With these coordinates you can calculate the distance, such the Euclidean distance with these functions
func distanceTravelled(xDist:Float, yDist:Float, zDist:Float) -> Float{
return sqrt((xDist*xDist)+(yDist*yDist)+(zDist*zDist))
}
func distanceTravelled(between v1:SCNVector3,and v2:SCNVector3) -> Float{
let xDist = v1.x - v2.x
let yDist = v1.y - v2.y
let zDist = v1.z - v2.z
return distanceTravelled(xDist: xDist, yDist: yDist, zDist: zDist)
}
Remember to convert the coordinates of the nodes to worldCoordinates -> node.worldPosition with node an instance of SCNNode
According to this question, using == and != should let you check for equality between two CGPoint objects.
However, the code below fails to consider two CGPoint objects as equal even though they output the same value.
What is the right way to check equality among CGPoint objects?
Code:
let boardTilePos = boardLayer.convert(boardTile.position, from: boardTile.parent!)
let shapeTilePos = boardLayer.convert(tile.position, from: tile.parent!)
print("board tile pos: \(boardTilePos). active tile pos: \(shapeTilePos). true/false: \(shapeTilePos == boardTilePos)")
Output:
board tile pos: (175.0, 70.0). active tile pos: (175.0, 70.0). true/false: false
Unfortunately, what you see in the console is not what your real value is.
import UIKit
var x = CGPoint(x:175.0,y:70.0)
var y = CGPoint(x:175.0,y:70.00000000000001)
print("\(x.equalTo(y)), \(x == y),\(x),\(y)")
The problem is, the console only allows for 10-16 but in reality your CGFloat can go even lower than that because on 64bit architecture, CGFloat is Double.
This means you have to cast your CGPoint values to a Float if you want to get equality that will appear on the console, so you need to do something like:
if Float(boxA.x) == Float(boxB.x) && Float(boxA.y) == Float(boxB.y)
{
//We have equality
}
Now I like to take it one step further.
In most cases, we are using CGPoint to determine points on the scene. Rarely do we ever want to be dealing with 1/2 points, they make our lives just confusing.
So instead of Float, I like to cast to Int. This will guarantee if two points are lying on the same CGPoint in scene space
if Int(boxA.x) == Int(boxB.x) && Int(boxA.y) == Int(boxB.y)
{
//We have equality
}
I'm providing an alternate answer since I don't agree with Knight0fDragon's implementation. This is only if you want to deal with factions of a point. If you only care about points in whole numbers, see Knight0fDragon's answer.
You don't always have the luxury of logging points to the console, or seeing if you're trying to compare points that are the victim of floating point math, like comparing (175.0, 70.0) to (175.0, 70.00001) (which both log as (175.0, 70.0) in the console). Yes, truncating to Int is a great way of understanding why two points that appear to print to the console as equal aren't. But it's not a catch all solution one should use for comparing every point. Depending on what level of precision you need, you want to take the absolute value of the difference of both x and y for each point, and see if it is in an acceptable range of a delta you specify.
var boxA = CGPoint(x:175.0, y:70.0)
var boxB = CGPoint(x:175.0, y:70.00000000000001)
let delta: CGFloat = 0.01
if (fabs(boxA.x - boxB.x) < delta) &&
(fabs(boxA.y - boxB.y) < delta) {
// equal enough for our needs
}
The answer to the question "What is the right way to check equality among CGPoint objects?" really depends on the way you compare floating point numbers.
CGPoint provides its own comparison method: equalTo(_ point2: CGPoint)
Try this:
shapeTilePos.equalTo(boardTilePos)
In reference to this question
Drawing a line between two points using SceneKit
I'm drawing a line in 3D and want to make it thicker by using this code
func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
//Makes the lines thicker
glLineWidth(20)
}
but it doesn't work, iOS 8.2.
Is there another way?
Update
From the docs
https://developer.apple.com/library/prerelease/ios/documentation/SceneKit/Reference/SCNSceneRendererDelegate_Protocol/index.html#//apple_ref/occ/intfm/SCNSceneRendererDelegate/renderer:updateAtTime:
I did add SCNSceneRendererDelegate and a valid line width but still could not get the line width to increase.
You cannot assign any number to glLineWidth().
You can check the range of possible values of glLineWidth()by:
glGetFloatv(GL_LINE_WIDTH_RANGE,sizes);
One crazy idea is to use a cylinder for drawing lines ;). I use it when I want to have nice and controllable lines but I am not aware of a handy OpenGl function to do so.
#G Alexander: here you go my implementation of cylinder. It is a bit tedious but it is what I have at the moment.
If you give me points p0 and p1, Vector normal=(p1-p0).normalize() would be the axis of the cylinder.
pick point p2 that is not on the vector Normal.
q=(p2-p0).normalize();
normal.crossproduct(q)=v0;
normal.crossproduct(v0)=v1;
Having these two vectors you can have circles with any radius that are stacked along the axis of the cylinder using the following function (A cylinder is a stack of circles):
public Circle make_circle(Point center, Vector v0, Vector v1, double radius)
{
Circle c;
for (double i = 0; i < 2 * Math.PI; i += 0.05)
{
Point p = new Point(center + radius * Math.Cos(i) * v0 + radius * Math.Sin(i) * v1);
c.Add(p);
}
return c;
}
You only need to make circles using this function along the axis of the cylinder:
List<Circle> Cylinder = new List<Circle>();
for(double i=0;i<1;i+=0.1)
{
Cylinder.add( make_circle(P0+i*normal, v0, v1,radius);
}
Now you should take two consecutive circles and connect them with quads by sampling uniformly.
I have implemented it this way since I had circles implemented already.
A simpler way to implement is make the circle along the x axis and then rotate and translate it to p0 and make it align with normal or to use gluCylinder if you are the fan of Glu....
Hopefully it works for you.
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))
}
}