So I have a bit of a project I am trying to do. I am trying to get the devices rotation relative to gravity, and translation from where it started. So basically getting "tracking" data for the device. I plan to basically apply this by making a 3d pt that will mimic the data I record from the device later on.
Anyway to attempt to achieve this I thought it would be best to work with scene kit that way I can see things in 3 dimensions just like the data I am trying to record. Right now I have been trying to get the ship to rotate so that it always looks like its following gravity (like its on the ground or something) no mater what the device rotation is. I figure once I have this down it will be a sinch to apply this to a point. So I made the following code:
if let attitude = motionManager.deviceMotion?.attitude {
print(attitude)
ship.eulerAngles.y = -Float(attitude.roll)
ship.eulerAngles.z = -Float(attitude.yaw)
ship.eulerAngles.x = -Float(attitude.pitch)
}
When you only run one of the rotation lines then everything is perfectly. It does behave properly on that axis. However when I do all three axis' at once it becomes chaotic and performs far from expected with jitter and everything.
I guess my question is:
Does anyone know how to fix my code above so that the ship properly stays "upright" no matter what the orientation.
J.Doe!
First there is a slight trick. If you want to use the iphone laying down as the default position you have to notice that the axis used on sceneKit are different then those used by the DeviceMotion. Check the axis:
(source: apple.com)
First thing you need to set is the camera position. When you start a SceneKit project it creates your camera in the position (0, 0, 15). There is a problem with that:
The values of eulerAngles = (0,0,0) would mean the object would be in the plane xz, but as long as you are looking from Z, you just see it from the side. For that to be equivalent to the iphone laying down, you would need to set the camera to look from above. So it would be like you were looking at it from the phone (like a camera, idk)
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 15, z: 0)
// but then you need to make the cameraNode face the ship (the origin of the axis), rotating it
cameraNode.eulerAngles.x = -Float(M_PI)*0.5 //or Float(M_PI)*1.5
With this we are going to see the ship from above, so the first part is done.
Now we gotta make the ship remain "still" (facing the ground) with the device rotation.
//First we need to use SCNRendererDelegate
class GameViewController : UIViewController SCNSceneRendererDelegate{
private let motion = CMMotionManager();
...
Then on viewDidLoad:
//important if you remove the sceneKit initial action from the ship.
//The scene would be static, and static scenes do not trigger the renderer update, setting the playing property to true forces that:
scnView.playing = true;
if(motion.deviceMotionAvailable){
motion.startDeviceMotionUpdates();
motion.deviceMotionUpdateInterval = 1.0/60.0;
}
Then we go to the update method
Look at the axis: the axis Y and Z are "switched" if you compare the sceneKit axis and the deviceMotion axis. Z is up on the phone, while is to the side on the scene, and Y is up on the scene, while to the side on the phone. So the pitch, roll and yaw, respectively associated to the X, Y and Z axis, will be applied as pitch, yaw and roll.
Notice I've put the roll value positive, that's because there is something else "switched". It's kinda hard to visualize. See the Y axis of device motion is correlated to the Z axis of the scene. Now imagine an object rotation along this axis, in the same direction (clock-wise for example), they would be going in opposite directions because of the disposition of the axis. (you can set the roll negative too see how it goes wrong)
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
if let rot = motion.deviceMotion?.attitude{
print("\(rot.pitch) \(rot.roll) \(rot.yaw)")
ship.eulerAngles.x = -Float(rot.pitch);
ship.eulerAngles.y = -Float(rot.yaw);
ship.eulerAngles.z = Float(rot.roll);
}
Hope that helps! See ya!
Related
I am close to completing my first project in SceneKit but I'm struggling with the last few steps. It is probably easiest to explain my progress by sharing a short screen capture video of the Xcode Simulator displaying my current scene.
As you can see by the screen capture my project is composed of three elements (this is all done in code, I do not import any external assets):
outside box (defined via six SCNBox objects per corner)
inside sun (defined via a SCNTube object for the circle and UIBezierPath objects per "ray")
position of camera
Based on feedback I have committed the code to GitHub.
Right now the camera is allowed to rotate as seen in the screen capture but the centre of rotation of the camera and of the objects doesn't align so it appears to spin off-axis.
Here's where I want to get to:
correct camera position so that the combined box & sun is positioned directly in front of the camera, filling the screen
maintain the sun's position as being fixed (already done I guess)
allow the box to rotate freely in x, y & z around the sun based on touch input - so the user can "flick" the box and watch it flip and spin around the sun
The code structure is straight forward:
class GameViewController: UIViewController {
var gameView: SCNView!
var gameScene: SCNScene!
var cameraNode: SCNNode!
var targetCreationTime: TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
initView()
initScene() // createSun() and createCube() called here
initCamera()
}
And with respect to the camera position:
func initCamera() {
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
cameraNode.rotation = SCNVector4Make(1, 0, 0, .pi/2)
}
But what I've found is that despite playing around with the random cameraNode.position and cameraNode.rotation values the camera view doesn't seem to change.
My questions - any help will be greatly appreciated:
advice on repositioning the camera (what am I doing wrong?!) - once it's in the right place I can easily set "gameView.allowsCameraControl = false"
advice on how to enable the box to spin about its axis around the sun (while the sun remains fixed)
stretch goal! Any kind of general "check out this tutorial" type info on materials and lighting, and embedding this view into a SwiftUI view
Thanks!
I decided to stop fighting the point of rotation and instead reposition the elements around this.
One interesting thing, which I’ve mentioned at the start of the createBox() func.
// originally debugCube & debugNode were used for debugging the pivot point of the box
// but I found have this large node helped to balance out the centre of mass
// set to fully transparent and added to boxNode as final step after all other transformations
If you comment out the lines 19-26 plus 117 you will completely remove debugNode. And funnily enough when you do that the box stops spinning correctly. But you add it back in and everything is fixed. I’m guessing it’s adding “mass” to the overall node and helping lock the point of rotation to the correct position. So in the end I just made it transparent!
The final (version 1.0) code is posted on GitHub at github.com/LedenMcLeden/logo
Use this answer in post for your camera: 57586437, remove camera rotation and take camera control off. Rotate your box with a simple (I'd do an x,y,z independent spin just to verify it) spin so that you'll know if your pivot point is correct. It should be ok by default and spin in place right in front of the camera, but depends on how you built your cube.
If you added the sun and stuff as a subnode of your box, then you're probably in decent shape and the pieces will rotate together.
If you want to do camera rotations similar to cameraControl, then you'll need to add a gesture recognizer and then you can start experimenting with it.
Hope that helps!
I am quite new and experimenting with Apple's ARKit and have a question regarding rotation information of the ARCamera. I am capturing photos and saving the current position, orientation and rotation of the camera with each image taken. The idea is to create 2d plane nodes with these images and have them appear in another view in the same position/orientation/rotation (with respect to the origin) as when when they were captured (as if the images were frozen in the air when they were captured). The position information seems to work fine, but the orientation/rotation comes up completely off as I’m having a difficulty in understanding when it’s relevant to use self.sceneView.session.currentFrame?.camera.eulerAngles vs self.sceneView.pointOfView?.orientation vs self.sceneView.pointOfView?.rotation.
This is how I set up my 2d image planes:
let imagePlane = SCNPlane(width: self.sceneView.bounds.width/6000, height: self.sceneView.bounds.height/6000)
imagePlane.firstMaterial?.diffuse.contents = self.image//<-- UIImage here
imagePlane.firstMaterial?.lightingModel = .constant
self.planeNode = SCNNode(geometry: imagePlane)
Then I set the self.planeNode.eulerAngles.x to the value I get from the view where the image is being captured using self.sceneView.session.currentFrame?.camera.eulerAngles.xfor x (and do the same for y and z as well).
I then set the rotation of the node as self.planeNode.rotation.x = self.rotX(where self.rotX is the information I get from self.sceneView.pointOfView?.rotation.x).
I have also tried to set it as follows:
let xAngle = SCNMatrix4MakeRotation(Float(self.rotX), 1, 0, 0);
let yAngle = SCNMatrix4MakeRotation(Float(self.rotY), 0, 1, 0);
let zAngle = SCNMatrix4MakeRotation(Float(self.rotZ), 0, 0, 1);
let rotationMatrix = SCNMatrix4Mult(SCNMatrix4Mult(xAngle, yAngle), zAngle);
self.planeNode.pivot = SCNMatrix4Mult(rotationMatrix, self.planeNode.transform);
The documentation states that eulerAngles is the “orientation” of the camera in roll, pitch and yaw values, but then what is self.sceneView.pointOfView?.orientation used for?
So when I specify the position, orientation and rotation of my plane nodes, is the information I get from eulerAngles enough to capture the correct orientation of the images?
Is my approach to this completely wrong or am I missing something obvious? Any help would be much appreciated!
If what you want to do is essentially create a billboard that is facing the camera at the time of capture then you can basically take the transform matrix of the camera (it already has the correct orientation) and just apply an inverse translation to it to move it to the objects location. They use that matric to position your billboard. This way you don't have to deal with any of the angles or worry about the correct order to composite the rotations. The translation is easy to do because all you need to do is subtract the object's location from the camera's location. One of the ARkit WWDC sessions actually has an example that sort of does this (it creates billboards at the camera's location). The only change you need to make is to translate the billboard away from the camer's position.
I have 2 questions in mind to place 3D object.
If a 3D object(for example Car) created to face door side of the car as camera's front face, how can I rotate the 3D object to make it normal? Like, rotate the car to make engine side of a car as camera's front face.
2.How to place the 3D object on Plane Surface by facing the object towards the camera. Move the Phone camera to see the backside of the object.
I am using XCode 9.1 with iOS 11 and above, ARKIT
To rotate the car node you can just adjust the eulerAngles until the car is facing the camera.
node?.eulerAngles = SCNVector3Make(0,Float(degToRadians(degrees:180 )),Float(degToRadians(degrees: 90)))
rotated this van around to front facing.
eulerAngles are in radians, so a handy conversion to degrees:
public func degToRadians(degrees:Double) -> Double
{
return degrees * (M_PI / 180);
}
You can also do the same rotation on the van node using an animation
let rotate = SCNAction.rotateBy(x: CGFloat(degToRadians(degrees: -90)), y: 0, z: 0, duration: 1)
node.runAction(rotate)
You can use SCNLookAtConstraint which follows the pointOfView along the negative z-axis of the parentNode.
So if the car’s negative z-axis is the camera facing the side doors... as you rotate the camera, around the car.... the car will swivel always maintaining that view.
let constraint = SCNLookAtConstraint(target:sceneView.pointOfView)
constraint.isGimbalLockEnabled = true
node.constraints = [constraint]
Inside Xcode, the screenshot below shows how you can change the rotation of the van using the euler angles (highlighted in red). The view should be “front” camera (also highlighted in red). The animation can also be done inside Xcode via the animation panel, select “rotate Action” & drag into the Actions timeline in the bottom -middle
I have just jumped in to using scene kit for iOS for the first time. What I am trying to do is import a .dae file, and rotate the object in code. What is happening is that when I import my file, I see that it is oriented the way I would expect.
This is great, now I want to rotate it so that the longer end goes horizontally. With my coordinate system this should be a 90 degree rotation around the Z axis. Doing that in the graphical part of xcode shows this.
The problem comes in when I start to manipulate this in code. If I undo the rotation in the graphical interface and try to perform it in code I get unexpected behavior.
If I start with no code changes, the simulator shows this
If I perform a code rotation around the Z axis with this code
// create a new scene
SCNScene *scene = [SCNScene sceneNamed:#"art.scnassets/board.dae"];
SCNNode *board = [scene.rootNode childNodeWithName:#"board" recursively:NO];
board.rotation = SCNVector4Make(0, 0, 1, M_PI/2);
I do not see the expected behavior in the simulator, I get this
Which does not show much detail but the board has been rotated around the Vertical axis, which I thought would be the Y axis. This led me to try and rotate around the Y axis for kicks and grins. Here is the code for that.
// create a new scene
SCNScene *scene = [SCNScene sceneNamed:#"art.scnassets/board.dae"];
SCNNode *board = [scene.rootNode childNodeWithName:#"board" recursively:NO];
board.rotation = SCNVector4Make(0, 1, 0, M_PI/2);
This yields the following, which is what I want.
Now for the real question, why do I have to rotate around a different axis in code? In this instance I was able to make it work, but in others like my camera, I can not. I think there must be some sort of coordinate shift that I am missing once something goes to code.
Thanks in advance.
Okay, this is driving me crazy. I've looked through tons of examples and can't seem to get quite what I need. I'm using XNA and I have a plane of vertices and my camera is up in the sky looking down on the vertices.
What I want is to rotate the camera around on the Y axis, basically get the same result as adjusting its YAW. However whenever I try to rotate around on the Y axis or adjust its YAW nothing actually happens. I can however get the effect I want by creating a Y rotation matrix on the world, but that doesn't feel like the "correct" way of doing it, I want the camera itself to spin and not the world. Here's a code snippet for what I have:
cameraPosition = Vector3.Transform(new Vector3(
cameraOffset.X - cameraOffset.X,
zoomAmount,
cameraOffset.Z - cameraOffset.Z),
Matrix.CreateRotationY(rotationAngle)) + cameraOffset;
view = Matrix.CreateLookAt(cameraPosition, cameraTarget, new Vector3(0, 0, 1));
Thanks!
Since you have the camera looking straight down, it sounds like what you want is for the camera to roll in local space. To roll your camera, you must rotate the Up vector you are feeding to the CreateLookAt(). Like this:
Vector3 newUp = Vector3.Transform(Vector3.UnitZ, Matrix.CreateRotationY(rotationAngle));
view = Matrix.CreateLookAt(cameraPosition, cameraTarget, newUp);