How to setup camera to point at object - ios

In my app I load models from different files (format is the same) and they have different geometry: big, small, wide, etc. I have object and camera position hard coded and for some cases I don't see anything because camera not point to object.
Maybe there is a way to normalise model before adding it to scene.
Update.
With Moustach answer I came up to following solution:
// import object from file
SCNNode *object = [importer load:path];
object.position = SCNVector3Make(0, 0, 0);
[scene.rootNode addChildNode:object];
// create and add a camera to the scene
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
// to avoid view clipping
cameraNode.camera.automaticallyAdjustsZRange = YES;
// set camera position to front of object
SCNVector3 sphereCenter;
CGFloat sphereRadius;
[object getBoundingSphereCenter:&sphereCenter radius:&sphereRadius];
cameraNode.position = SCNVector3Make(sphereCenter.x, sphereCenter.y, sphereCenter.z + 2 * sphereRadius);
[scene.rootNode addChildNode:cameraNode];
Works well for me.

You can calculate the bounding box of your mesh, and scale it based on the number you get to make it the same size as other objects.

Related

Vuforia iOS Swift scene not visible

I downloaded sample code from https://github.com/yshrkt/VuforiaSampleSwift to implement Vuforia with swift. It didn't work oob with the latest SDK, but I have successfully loaded xml/dat dataSet, and when I point to target method ViewController.createStonesScene(with view: VuforiaEAGLView) -> SCNScene is called. Unfortunately I don't see anything on camera preview. There should be a plane with background color, but nothing is added. Can someone help me resolve this? I wan't to display a simple plane for start. My project is at: https://www.dropbox.com/s/fk71oay1sopc1vp/test.zip?dl=1
Please update Vuforia License key in AppDelegate, I can't provide it as I'm not the owner of this application.
I get to the point, that I know, that scene is being displayed, but camera transformations must be wrong. Either _cameraNode.camera.projectionTransform or _cameraNode.transform (or both).
I added zNear = 0.01 to cameraNode, which was a little step. Now If I just apply cameraNode.transform and disable cameraNode.camera.projectionTransform I can see a node of correct size, but it's rotated 90 degrees, and also when I move camera it moves 90 degrees wrong (moving up/down moves it left/right). A little bit of code that does this is below:
- (void)setNeedsChangeSceneWithUserInfo: (NSDictionary*)userInfo {
SCNScene* scene = [self.sceneSource sceneForEAGLView:self userInfo:userInfo];
if (scene == nil) {
return;
}
SCNCamera* camera = [SCNCamera camera];
_cameraNode = [SCNNode node];
_cameraNode.camera = camera;
_cameraNode.camera.zNear = 0.01;
// _cameraNode.camera.projectionTransform = _projectionTransform;
[scene.rootNode addChildNode:_cameraNode];
_renderer.scene = scene;
_renderer.pointOfView = _cameraNode;
}
// Set camera node matrix
- (void)setCameraMatrix:(Vuforia::Matrix44F)matrix {
SCNMatrix4 extrinsic = [self SCNMatrix4FromVuforiaMatrix44:matrix];
SCNMatrix4 inverted = SCNMatrix4Invert(extrinsic);
_cameraNode.transform = inverted;
}
- (void)setProjectionMatrix:(Vuforia::Matrix44F)matrix {
_projectionTransform = [self SCNMatrix4FromVuforiaMatrix44:matrix];
// _cameraNode.camera.projectionTransform = _projectionTransform;
}
So now I need to rotate whole scene 90 degrees. I think it might have been in _cameraNode.camera.projectionTransform = _projectionTransform;, but when I enable this, I can't see anything anymore. How to apply 90" rotation to this scene?
Here is a video of what I mean: https://www.dropbox.com/s/z6pwaztlfyad8fx/ScreenRecording_09-03-2018%2015-01-17.MP4?dl=0
I think this can be in VuforiaManager.mm:
// Cache the projection matrix
const Vuforia::CameraCalibration& cameraCalibration = Vuforia::CameraDevice::getInstance().getCameraCalibration();
_projectionMatrix = Vuforia::Tool::getProjectionGL(cameraCalibration, 0.05f, 5000.0f);
[_eaglView setProjectionMatrix:_projectionMatrix];
Anyone know how to fix this?

SceneKit nodesInsideFrustumWithPointOfView: unexpectedly returning empty

I am using the following method to determine which SCNNodes are visible by the camera.
[self.scnView nodesInsideFrustumWithPointOfView:cameraNode];
However the returned array is always empty.
I set up the scene up as follows:
-(void)setupScene{
scene = [SCNScene scene];
cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
[scene.rootNode addChildNode:cameraNode];
cameraNode.position = SCNVector3Make(0, 0, 0);
[scene.rootNode addChildNode:cameraNode];
self.scnView.scene = scene;
self.scnView.showsStatistics = YES;
self.scnView.backgroundColor = [UIColor clearColor];
}
At a random time, after the scene is created, I add a SCNNode to the scene:
testnode = [Testnode createNode];
testnode.position = SCNVector3Make(0, 0, -10);
[self.scnView.scene.rootNode addChildNode:testnode];
On my device, the node "testnode" is visible on my screen yet nodesInsideFrustumWithPointOfView: returns nothing.
EDIT: I tried changing the point of view to a spot light object and test whether "testnode" is inside its frustum. Here is what I see on screen: http://imgur.com/a/C3XGu Yet the array still returns empty. The testnode is the white cube.
Adding this for better visibility. Thank Crashalot for this.
Basically, if the SCNNode you are trying to detect using [scnView nodesInsideFrustumWithPointOfView:] and [scnView isNodeInsideFrustum: withPointOfView:] is the child of a node with empty geometry, it won't be detected.
In my case, I added a plane geometry and set the material to transparent:
SCNNode *emptynode = [SCNNode node];
node.geometry = [SCNPlane planeWithWidth:1.0f height:2.0f];
node.geometry.firstMaterial.transparency = 0.0f;

Camera Rotation in SceneKit

I have posted a similar question to this
here , but this is different in that it deals with eular angles.
Given the setup that I had in the other post. A simple board that is on the screen, and a camera that is looking at it, I want to rotate the camera. For simplicity I am doing the entire camera in code. For context, per the other question and answer I have established that the board runs long on the Z axis, Shorter on the X axis, and the height is the Y axis.
Adding this code to the scene we can see my board running on the Z axis. I have raised the camera up on the Y axis a little to get a better view. My end goal is to get the board running longways accross the camera.
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
cameraNode.camera.zFar = 200;
cameraNode.camera.zNear = 0.1;
[scene.rootNode addChildNode:cameraNode];
cameraNode.position = SCNVector3Make(0, 5, 0);
cameraNode.eulerAngles = SCNVector3Make(0, 0, 0);
Gives
Great start. Then I try to rotate the camera so it is looking from the top, down on the board. My understanding it that I would need to rotate around the X-axis to accomplish this. I did this by trying the following.
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
cameraNode.camera.zFar = 200;
cameraNode.camera.zNear = 0.1;
[scene.rootNode addChildNode:cameraNode];
cameraNode.position = SCNVector3Make(0, 50, 0);
cameraNode.eulerAngles = SCNVector3Make(0, 0, -M_PI/2);
The iOS documentation states that the rotation in eulars is set with (z, y, x). This did not work, however, and only gave a blank screen. I then started experimenting and found that rotation around the Z axis would get me in the right direction.
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
cameraNode.camera.zFar = 200;
cameraNode.camera.zNear = 0.1;
[scene.rootNode addChildNode:cameraNode];
cameraNode.position = SCNVector3Make(0, 50, 0);
cameraNode.eulerAngles = SCNVector3Make(-M_PI/2, 0, 0);
This rendered this screen.
This didn't make sense but I went ahead with it and eventually found that by also Rotating around the Y axis I could get my desired screen.
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = [SCNCamera camera];
cameraNode.camera.zFar = 200;
cameraNode.camera.zNear = 0.1;
[scene.rootNode addChildNode:cameraNode];
cameraNode.position = SCNVector3Make(0, 50, 0);
cameraNode.eulerAngles = SCNVector3Make(-M_PI/2, -M_PI/2, 0);
The y rotation was also a little baffling because I would have expected to have to rotate around the Z axis.
While this code works I have a similar question to my other post. Why do I have to rotate around the Z axis instead of the X axis for my first rotation. I think I might just not understand eular angles that well.
Thanks in advance
Edit:
as #mnuages pointed out, the documentation online states that this vector is in (x,y,z). This is contrary to the header documentation which I would normally use as can be seen here
It would appear that this is just a bug/typo in Apple's code. Thanks for clearing it up.
[Comment does not fit in a comment]
Please note that the documentation states that
The order of components in this vector matches the axes of rotation:
Pitch (the x component) is the rotation about the node’s x-axis.
Yaw (the y component) is the rotation about the node’s y-axis.
Roll (the z component) is the rotation about the node’s z-axis.

scenekit node rotation and movement in 3D Space

Let's say we have a SCNNode and we want to rotate, change flight direction and move in space with no gravity. The node currently is just a camera. actually it should react to gyroscope accelerometer data. i think scene update can be done in - (void)renderer:(id <SCNSceneRenderer>)aRenderer didSimulatePhysicsAtTime:(NSTimeInterval)time.
The problem I'm facing is how to utilise the gyroscope & accelerometer data for calculating flight direction, and how to calculate the rotation direction & movement. I refreshed my memory about 3D rotation matrix for each axis(x,y,z) but still missing above mentioned part to solve this problem. May be thinking very complicated and the solution is very easy.
- (void)viewDidLoad
{
SCNView *scnView = (SCNView *) self.view;
SCNScene *scene = [SCNScene scene];
scnView.scene = scene;
_cameraNode = [[SCNNode alloc] init];
_cameraNode.camera = [SCNCamera camera];
_cameraNode.camera.zFar = 500;
_cameraNode.position = SCNVector3Make(0, 60, 50);
_cameraNode.rotation = SCNVector4Make(1, 0, 0, -M_PI_4*0.75);
[scene.rootNode addChildNode:_cameraNode];
scnView.pointOfView = _cameraNode;
[self setupAccelerometer];
}
- (void)setupAccelerometer
{
_motionManager = [[CMMotionManager alloc] init];
GameViewController * __weak weakSelf = self;
if ([[GCController controllers] count] == 0 && [_motionManager isAccelerometerAvailable] == YES) {
[_motionManager setAccelerometerUpdateInterval:1/60.0];
[_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
[weakSelf accelerometerDidChange:accelerometerData.acceleration];
}];
}
}
- (void)accelerometerDidChange:(CMAcceleration)acceleration
{
//acceleration.x
//acceleration.y
//acceleration.z
// now we have the data and saved somewhere
}
If you want to combine gyroscope and accelerometer data to get an orientation, you'd probably be better off letting CoreMotion do that for you. The methods listed under Managing Device Motion Updates in the docs get you CMDeviceMotion objects; from one of those you can get a CMAttitude that represents an orientation, and from that you can get a rotation matrix or quaternion that you can apply to a node in your scene. (Possibly after further transforming it so that a neutral orientation fits into your scene the way you want.)
After you've oriented the node the way you want, getting a flight direction from that should be pretty simple:
Choose a flight direction in terms of the local coordinate space of the node and make a vector that points in that direction; e.g. a camera looks in the -z direction of its node, so if you want the camera node to look along its flight path you'll use a vector like {0, 0, -1}.
Convert that vector to scene space with convertPosition:toNode: and the scene's rootNode. This gets you the flight direction in terms of the scene, taking the node's orientation into account.
Use the converted vector to move the node, either by assigning it as the velocity for the node's physics body or by using it to come up with a new position and moving the node there with an action or animation.

Physics Issue When Loading SCNScene from .dae

I am attempting to load a scene that is currently just 3 walls, a ceiling and a floor. I am loading the scene that I created in blender and load it in fine. However, a SCNNode with a geometry of a SCNBox just falls right through. The box has a dynamic physics body attached to it and the I am manually setting the walls/floor to be static nodes. Below is the code I am using to set up the scene and add the box, I can also post my .dae if needed. Anyone have any ideas as to what might be going on?
//Load the scene from file
SCNScene *scene = [SCNScene sceneNamed:#"mainScene.dar"];
//Get each node in the scene, and give it a static physics bodt
for (SCNNode *node in [[scene rootNode] childNodes]) {
SCNPhysicsBody *staticBody = [SCNPhysicsBody staticBody];
staticBody.restitution = 1.0;
node.presentationNode.physicsBody = staticBody;
NSLog(#"node.name %#",node.name);
}
//Create box
SCNNode *block = [SCNNode node];
block.position = SCNVector3Make(0, 0, 3);
//Set up the geometry
block.geometry = [SCNBox boxWithWidth:.8 height:.8 length:.8 chamferRadius:0.05];
block.geometry.firstMaterial.diffuse.mipFilter = SCNFilterModeLinear;
block.castsShadow = YES;
//Make it blue
for (SCNMaterial *mat in block.geometry.materials) {
mat.emission.contents = [UIColor blueColor];
}
//Add physics body
SCNPhysicsBody *body = [SCNPhysicsBody staticBody];
body.mass = 5;
body.restitution = .7;
body.friction = 0.5;
block.physicsBody = body;
//Add the node to the scene
[[scene rootNode] addChildNode:block];
In response to ricksters answer I tried to create custom geometry for each new node, but my box still falls through. Here is the code that I am using for the custom geometry. This replaces the for-in in the original code.
//Get each node in the scene, and give it a static physics bodt
for (SCNNode *node in [[scene rootNode] childNodes]) {
SCNGeometry *geometry = [SCNBox boxWithWidth:node.scale.x height:node.scale.y length:node.scale.z chamferRadius:0.0];
SCNPhysicsShape *physicsShape = [SCNPhysicsShape shapeWithGeometry:geometry options:nil];
SCNPhysicsBody *staticBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeStatic shape:physicsShape];
staticBody.restitution = 1.0;
node.physicsBody = staticBody;
}
So I had a similar issue. I found that the issue stemmed from how the file was created in the 3d modeling software. I was using Blender to test this; I made a plane with a box, added a physics body to the box and the plane and the box fell through. I realized this had to do with the scale. In Blender, I applied the object transformation which reset the scale to 1.0 1.0 1.0 by pressing CTRL A and selecting the scale option. So ultimately what seems to be happening is that SceneKit uses the base geometry ignoring the transform of the geometry. What you are seeing on the screen is the base geometry with the node's transform applied. Set the transform to identity before exporting the Collada file and you should be set.
Depending on how the geometry for those nodes is built in the DAE, SceneKit may not be able to automatically construct a physics shape that does what you want.
Instead, use bodyWithType:shape: to create exactly the collision shape you want, separate from the node's visible geometry. Creating physics shapes from parametric geometry (e.g. SCNBox, SCNSphere) works best.

Resources