So I have a node in the scene. The node is a text node. I want the text node to appear like it always points to the camera as I pan around. I am referring to the camera we get when we use allowsCameraControl = true. I tried to set a look at constraint to the current Point of view of the scene on the text node. However when I move around the scene using the default controls the text does not "look at" the camera but it turns around like any other node in the scene.
What am I doing wrong?
The constraint I used is this:
let lookAt = SCNLookAtConstraint(target: pov) // pov is the camera
textNode.constraints = [lookAt]
Ok, I found the problem. The node was not getting initialized correctly. I was using this code:
let textNode:SCNNode? = self.scnKitView.scene?.rootNode.childNodeWithName("textNode", recursively: false)
To make it work I set the recursively flag to true instead of false.
I thought that by providing the name of the node SceneKit would just find it without having to set that flag to true.
Related
I created SCNNodes with a SCNCylinder geometry to represent lines between points (ie: SCNSphere).
It does work very well.
Now i am moving one point, and want to move the 2 lines that were "linked" to this point. Lets concentrate on only the first line for simplicity.
To move a line:
I move the center of the cylinder node and update its length.
I use simdLook to change its orientation.
lineNode.simdLook(at: targetPoint, up: shapeRootNode.WorldUp, localFront: lineNode.WorldUp)
The line correctly moves to the center point, and has the correct length, but gets an incorrect orientation every 2 calls. After the 1st call it is correct. After the 2nd call it is perpendicular to the orientation it should have. After the 3rd call it is correct. Etc... 😭
✅ I verified that targetPoint is correct, the worldUp of the scene (on its root node) is constant. So this leaves the worldUp of the line node ❌.
This 3rd parameter of simdLook is a local front. As a SCNCylinder is built vertically, its local worldUp is the line direction. so it should work 😶?
Ok i found a workaround.
I reset lineNode.Orientation to SCNQuaternion's Identity before using Look.
So the issue is Look incorrectly uses the node current's Orientation property to compute the new ... Orientation property for the same node!
This is silly.
I'm trying to point a circle with arrow at the top at an scnnode in scenekit, but keep it constrained or flat toward the point of view at all times. It seems that with a look at constraint, the billboard constraint gets ignored.
Here is the setup for constraints:
let billboardConstraint = SCNBillboardConstraint ()
billboardConstraint.freeAxes = SCNBillboardAxis.Y
let lookatConstraint = SCNLookAtConstraint ( target: targetNode )
lookatConstraint.localFront = SCNVector3Make ( 0, 1, 0 )
lookatConstraint.worldUp = SCNVector3Make ( 0, 1, 0 )
arrowNode.constraints = [billboardConstraint, lookatConstraint]
self.sceneView.pointOfView?.addChildNode ( arrowNode )
Here are example images of the issue. The first image is how I want to keep it, the second image shows the flattening that I can't seem to control:
No matter what I try, it doesn't stay facing the camera. I've tried equating the z distance for both the arrow node and the target node (still rotates the arrow node undesirably), tried setting a gimbal lock (flips the arrow node out), tried to adjust the angles for the arrow node to keep it "facing" the point of view, and tried an SCNCone as a pointer.
I also tried just moving this arrow node into an imageview in the overlay, with an invisible arrow node inside Scenekit, but couldn't get the math right when trying to CGAffineTransform the 2d UIImageView. I tried getting the rotation vector for the SCNNode that has the SCNLookatConstraint, tried projectpoint, etc. Not quite getting it. Maybe I should've paid more attention in high school :-(
Anyone have any ideas:
This post: 59251351 - it's like a 3d box with a tube (a tank with a gun), that targets a specific node - "aims" at it. That's your arrow, so first - make sure that you have set it up with the correct rotation from the beginning, otherwise it will never work. Arrow should be a subnode so that your main node and all subnodes rotate together as you set the constraints for the main node.
The example is a fixed -z for related nodes and the point of view doesn't change. Assuming you want to move the camera forward, then you need a way to maintain perspective - that's keeping Z for all in question at the same distance from the camera.
So, if your target node and indicator can stay on the same z distance plane (as you indicated in your example that it can work that way), I "think" this should help. You might set your target node slightly off on z compared to your arrrow nodes, something insignificant such as .001 - just so the lookAt math doesn't actually return a (NAN) though some happenstance.
If it's more complex than that, then someone with more math skills is required.
Hope that helps
Is there a way to solidify a SCNNode scale and orientation by recalculating its geometry?
Basically I am loading a SCNNode out of a scn file that was converted from a sketchup file that was exported to a DAE file. Because ARKit works in meters and with a different axis orientation I have to set the loaded SCNNode’s scale (to 0.0254) and eulerangle (x -90deg) to correctly show it. This all works fine however thus scaling and rotation messes up some logic down the line because this logic also uses rotation thus overriding the previous one... :(
I think it would be great if I could simply tell the SCNNode to recalculate its geometry based on its current scale, orientation... (basically its transformation matrix) which would result in an SCNNode with transformation matrix the zero matrix....
If you “simply” want to tell the node to scale its vertices permanently, you will have to write a function to do so as there is no standard option.
That function should read the vertex source of the node’s geometry into an array of vectors, then use GLKMatrix4MultiplyAndProjectVector3 to apply the transformation to each vector, and then create a new SCNGeometry with the new verts as a source.
GLKMatrix4 scalemat = GLKMatrix4MakeScale(aNode.scale.x, aNode.scale.y, aNode.scale.z);
for (HEVertex* vert in toBeTransformedVerts) {
vert.pos = SCNVector3FromGLKVector3(GLKMatrix4MultiplyAndProjectVector3(scalemat, SCNVector3ToGLKVector3(vert.pos)) );
}
//reset node scale property.
aNode.scale = SCNVector3Make(1.0, 1.0, 1.0);
HEVertex is a class I use to store vertices, the pos property is a SCNVector3. In your case you would have to read the vertex source of the .geometry of the SCNNode into an array of SCNVector3 and loop through those instead.
After transforming the position of each vertex, you need to update the node's geometry. I.e. something like this:
SCNGeometrySource *_vertexSource =
[SCNGeometrySource geometrySourceWithVertices:_meshVertices count:_vertexCount];
aNode.geometry = [SCNGeometry geometryWithSources:#[_vertexSource, _aNode.geometry.geometrySources[1], _aNode.geometry.geometrySources[2]] elements:#[_aNode.geometry.geometryElements.firstObject]];
That is just a rough example, it updates only the vertex source, and reuses the normal and color geometry sources and the geometryElement, which can differ highly per model.
Very doable, but not nearly as simple as re-exporting the model with the appropriate size.
If the node doesn't have any subnodes, you can simply parent your node to an empty node, perform the desired transforms on your node, then call flatten on the empty parent node. Your node's center and orientation will match what was the empty parent node. If your node has a tree of child nodes, you'll need to do this recursively.
As an alternative to more complicated approaches you can easily accomplish this with a simple child node...
Create a new node
Move the geometry (and any other node properties you wish) from your original node to the new node
Add the new node as a child of the original node
Apply the scale transform to the child node.
Essentially, after step 3 you should be back exactly where you were before doing anything, except you now have a new child node to apply your scale transforms to, leaving the parent node alone so it can use the identity matrix (or whatever matrix you want.)
A similar approach is to use the existing node as-is...
Create a new parent node
Add it to the same parent as your existing node
Move your existing node from the old parent to this new parent
Copy the transform from your existing child node to the new parent node
Replace the transform on the child node with the scale transform only
Choosing between the two really depends on how much stuff you have on your node that you want to move as well as what transforms it originally had on it. (i.e. if you scale a node, it may be out of place from its original position relative to the model.) You choose which of the above you wish to do based on that information.
That said, if it's just the geometry alone and scaling it in-place works, go with the first. If there are lots of node properties, go with the second. Point being, you're adding a new node specifically to apply the scaling to your existing node, no direct geometry modifications needed.
I am working on a AR based POC using the sample given by Apple.
Is there a way to place a 3D object above another 3D object in SceneKit?
For example I have placed a table above which I have to place something else like a flower vase. How to achieve this?
Because as far as I know, ARKit is detecting only floor surface and if I try to place the flower vase over the already kept table, it is placing it under the table overlapping the existing 3D object. Is this doable?
When you're making hitTest, you could check if there's any nodes:
let results = sceneView.hitTest(checkLocation, options: [.boundingBoxOnly: true])
guard let node = results.first?.node else { return }
Then you can place your new node as child node for touched node.
I have a text node. I am telling the node to always look at the camera node like so:
let lookAt = SCNLookAtConstraint(target: cameraNode)
textNode.constraints = [lookAt]
Before I apply that constraint the text looks like this: (see image)
If I apply that constraint, the node look flipped like in the image (see image).
So, the text does always keep that orientation and correctly look at the camera node, so the constraint is working.
I am trying to understand how the node "looks" at the target node though. I would obviously want the text to simply stay like shown in the first image as the camera moves around.
I tried to apply a rotation to the text to correct the behavior I get when applying the constraint but didn't work.
So, how can I get the text to have look at another node but not be flipped like in the second image?
The documentation for SCNLookAtConstraint notes:
When SceneKit evaluates a look-at constraint, it updates the constrained node's transform property so that the node's negative z-axis points toward the constraint's target node.
But SCNText geometry is created facing forward along its positive z-axis, so your constraint system needs to account for that in some way. I advise using SCNBillboardConstraint, which is designed for this kind of case. Its documentation says:
An SCNBillboardConstraint object automatically adjusts a node's orientation so that its local z-axis always points toward the pointOfView node currently being used to render the scene.
With this, you shouldn't need to reparent the node.
The "SCNBillboardConstraint object automatically adjusts a node's orientation so that its local z-axis always points toward the pointOfView node currently being used to render the scene."
textNode.constraints = [SCNBillboardConstraint()]
You can also modify lookAt constraint by specifying the axis through which it will "look" via the localFront property:
let lookAtConstraint = SCNLookAtConstraint(target: distantNode)
lookAtConstraint.localFront = SCNVector3Make(0, 1, 0)
node.constraints = [lookAtConstraint]
The above code will orient the node to distantNode via it's Y-axis.