I am making a fist person camera in Scenekit and the character is not moving relative to the rotation. Instead it is moving relative to the world axis. I want my game to be so when the player swipes forward, the character moves forward on its own x axis. Not along the x axis of the world. I tried to compensate of this with sine and cosine but it did not work. Here is my code:
func lookGestureRecognized(gesture: UIPanGestureRecognizer) {
let velocity = gesture.velocityInView(sceneView)
let rotationAngle = heroNode.presentationNode().rotation.w * heroNode.presentationNode().rotation.y
var impulse = SCNVector3Make(Float(velocity.x)/50, 0, Float(velocity.y)/50)
impulse = SCNVector3(x: impulse.x * cos(rotationAngle), y: 0, z: impulse.z * sin(rotationAngle))
heroNode.physicsBody?.applyForce(impulse, impulse: true)
}
This is still making the character move in the wrong direction of the swipe. Does anyone know how to make the character move relative to its rotation?
Thanks, please ask for clarification if needed
the rotation property is made of a rotation axis and a rotation angle. If you want to retrieve the rotation along the x axis you can use the eulerAngles property.
Related
I am using two virtual joysticks to move my camera around the scene. The left stick controls the position and the right one controls the rotation.
When using the right stick, the camera rotates, but it seems that the camera rotates around the center point of the model.
This is my code:
fileprivate func rotateCamera(_ x: Float, _ y: Float)
{
if let cameraNode = self.cameraNode
{
let moveX = x / 50.0
let rotated = SCNMatrix4Rotate(cameraNode.transform, moveX, 0, 1, 0)
cameraNode.transform = rotated
}
}
I have also tried this code:
fileprivate func rotateCamera(_ x: Float, _ y: Float)
{
if let cameraNode = self.cameraNode
{
let moveX = x / 50.0
cameraNode.rotate(by: SCNQuaternion(moveX, 0, 1, 0), aroundTarget: cameraNode.transform)
}
}
But the camera just jumps around. What is my error here?
There are many ways to handle rotation, some are very suitable for giving headaches to the coder.
It sounds like the model is at 0,0,0, meaning it’s in the center of the world, and the camera is tranformed to a certain location. In the first example using matrices, you basically rotate that transformation. So you transform first, then rotate, which yes will cause it to rotate around the origin (0,0,0).
What you should do instead, to rotate the camera in local space, is rotate the camera first in local space and then translate it to its position in world space.
Translation x rotation matrix results in rotation in world space
Rotation x translation matrix results in rotation in local space
So a solution is to remove the translation from the camera first (moving it back to 0,0,0), then apply the rotation matrix, and then reapply the translation. This comes down to the same result as starting with an identity matrix. For example:
let rotated = SCNMatrix4Rotate(SCNMatrixIdentity, moveX, 0, 1, 0)
cameraNode.transform = SCNMatrix4Multiply(rotated, cameraNode.transform)
I’m using ARKit with SceneKit. When user presses a button I create an anchor and to the SCNNode corresponding to it I add a 3D object (loaded from a .scn file in the project).
The 3D object is placed facing the camera, with the same orientation the camera has. I would like to make it look like the object is laying on a plane surface and not inclined if it is that way. So, if I got it right, I’d need to apply a rotation transformation so that it’s rotation around the X and Z axis become 0.
My attempt at this is: take the node’s x and z eulerAngles, invert them, and rotate that amount around each axis
let rotationZ = rotationMatrixAroundZ(radians: -node.eulerAngles.z)
let rotationX = rotationMatrixAroundX(radians: -node.eulerAngles.x)
let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)
node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))
This works all right for most cases, but in some the object is rotated in completely strange ways. Should I be setting the
rotation angle to anything else than just the inverse of the current Euler Angle? Setting the angles to 0 directly did not work at all.
I've come across this and figured out I was running into gimbal lock. The solution was to rotate the node around one axis, parent it to another SCNNode(), then rotate the parent around the other axis. Hope that helps.
You don't have to do the node transform on a matrix, you can simply rotate around a specific axis and that might be a bit simpler in terms of the logic of doing the rotation.
You could do something like:
node.runAction(SCNAction.rotateBy(x: x, y: y, z: z, duration: 0.0))
Not sure if this is the kind of thing you're looking for, but it is simpler than doing the rotation with the SCNMatrix4
Well, I managed a workaround, but I'm not truly happy with it, so I'll leave the question unanswered. Basically I define a threshold of 2 degrees and keep applying those rotations until both Euler Angles around X and Z are below the aforementioned threshold.
func layDownNode(_ node: SCNNode) {
let maxErrDegrees: Float = 2.0
let maxErrRadians = GLKMathDegreesToRadians(maxErrDegrees)
while (abs(node.eulerAngles.x) > maxErrRadians || abs(node.eulerAngles.z) > maxErrRadians) {
let rotationZ = -node.eulerAngles.z
let rotationX = -node.eulerAngles.x
let rotationTransformZ = rotationMatrixAroundZ(radians: rotationZ)
let rotationTransformX = rotationMatrixAroundX(radians: rotationX)
let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)
node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))
}
}
I try use lookat function rotate camera with pan gesture. I use swift and Metal(in this case Metal work the same with OpenGLES). Here is my code
The lookat function:
let kEye = V3f(0.0, 0.0, -2.0)
var ktarget = V3f(0.0, 0.0, 0.0)
let kUp = V3f(0.0, 1.0, 0.0)
var viewMatrix = lookAt(kEye, center: ktarget, up: kUp)
The pan gesture:
func pan(panGesture: UIPanGestureRecognizer){
if panGesture.state == UIGestureRecognizerState.Changed{
let pointInView = panGesture.locationInView(self.view)
let xDelta = (lastPanLocation.x - pointInView.x)/self.view.bounds.width * panSensivity
let yDelta = (lastPanLocation.y - pointInView.y)/self.view.bounds.height * panSensivity
lastPanLocation = pointInView
var viewDirection = rotationM3f(kUp, angle: Float(-xDelta)) * viewDirection
var toRotateAround = Cross(viewDirection, b: kUp)
viewDirection = rotationM3f(toRotateAround, angle: Float(-yDelta)) * viewDirection
ktarget = kEye + viewDirection
} else if panGesture.state == UIGestureRecognizerState.Began {
lastPanLocation = panGesture.locationInView(self.view)
}
}
At the beginning, it works fine, pan the camera after a while, the viewDirection and toRotateAround vector will become -0.0,-0.0,0.0, when finger move vertically but the camera does not look up and down, Anyone knows what is wrong in the code? Thanks~~~
You only modify the view direction (ktarget in the end) but forget about kUP. Once these 2 vectors become parallel the cross product is zero and everything breaks.
The solution you are looking for is recomputing the kUp vector by using a cross product of viewDirection and toRotateAround.
When using rotations like this you need to think of your data as base vectors and a position (location=kEye, forward=ktarget-keye, up=kUp, right=cross(forward, up)). The base vectors are always perpendicular to each other (I suggest them to be normalized as well) and when you rotate you always rotate one of these vectors around another base vectors and after the rotation you need to recompute the 3rd vector by using a cross product.
So to rotate left or right you would rotate the forward around up and then use a cross product between forward and up to get the right vector. (The right vector here is optional since you do not use it)
To rotate up or down you rotate forward vector around right vector and use a cross product between the forward and right to get the new top vector.
Then for tilting left or right you rotate up around forward and get the right vector with cross product of the 2 used vectors.
If you see the logic you will find out there are always 2 ways of rotating along one axis. For instance to rotate left or right you might as well rotate the right vector around up and find the new forward vector by using a cross product of right and up.
There is a trick though. The procedure described here works great for a free movement such as a flight simulation where you can "tilt". It is not appropriate for a movement such as for a first person shooter where up is always in the center of the screen horizontally (I hope you see the difference). To create this FPS way you actually do need to keep up as (0,1,0) but then forward must never be (0,1,0) but it can be (0.001, 0.09, 0) which is pretty close to looking directly upwards. So as long as you limit the upwards angle to some value you should be fine. There are other ways as well...
I'm trying to make a game where the sprite will always move to the right when hit by an object. However since the Sprite rotates constantly and the zero radians rotates with the Sprite causes my calculated magnitude to go the opposite direction if the sprite is facing left and hits the object. Is there a way to keep the direction of the magnitude always pointing to the right even if the zero is facing left?
// referencePoint = upper right corner of the frame
let rightTriangleFinalPoint:CGPoint = CGPoint(x: referencePoint.x, y: theSprite.position.y)
let theSpriteToReferenceDistance = distanceBetweenCGPoints(theSprite.position, b: referencePoint)
let theSpriteToFinalPointDistance = distanceBetweenCGPoints(theSprite.position, b: rightTriangleFinalPoint)
let arcCosineValue = theSpriteToFinalPointDistance / theSpriteToReferenceDistance
let angle = Double(acos(arcCosineValue))
let xMagnitude = magnitude * cos(angle)
let yMagnitude = (magnitude * sin(angle)) / 1.5
Not sure if this works for you:
I would use an orientation constraint to rotate the sprite. The movement can be done independent from the orientation in that case.
I made an tutorial some time ago: http://stefansdevplayground.blogspot.de/2014/09/howto-implement-targeting-or-follow.html
So I figured out what was going on.
It seems like the angle doesn't rotate with the Sprite like I originally thought and the vector that I am making is working with the code above. THE problem that I had was that I also set the collision bit for the objects which is wrong. If I only set the contact bit for the objects against the sprite the my desired outcome comes true.
I am trying to applyTorque to a node in my scene. The documentation states:
Each component of the torque vector relates to rotation about the
corresponding axis in the local coordinate system of the SCNNode
object containing the physics body. For example, applying a torque of
{0.0, 0.0, 1.0} causes a node to spin counterclockwise around its
z-axis.
However in my tests it seems that Physics animations do not affect actual position of the object. Therefore, the axis remain static (even though the actual node obviously moves). This results in the torque always being applied from the same direction (wherever the z axes was when the scene was initiated).
I would like to be able to apply torque so that it is always constant in relation to the object (e.g. to cause node to spin counterclockwise around z-axis of the node's presentationNode not the position node had(has?) when the scene was initiated)
SceneKit uses two versions of each node: the model node defines static behavior and the presentation node is what's actually involved in dynamic behavior and used on screen. This division mirrors that used in Core Animation, and enables features like implicit animation (where you can do things like set node.position and have it animate to the new value, without other parts of your code that query node.position having to working about intermediate values during the animation).
Physics operates on the presentation node, but in some cases--like this one--takes input in scene space.
However, the only difference between the presentation node and the scene is in terms of coordinate spaces, so all you need to do is convert your vector from presentation space to scene space. (The root node of the scene shouldn't be getting transformed by physics, actions, or inflight animations, so there's no practical difference between model-scene space and presentation-scene space.) To do that, use one of the coordinate conversion methods SceneKit provides, such as convertPosition:fromNode:.
Here's a Swift playground that illustrates your dilemma:
import Cocoa
import SceneKit
import XCPlayground
// Set up a scene for our tests
let scene = SCNScene()
let view = SCNView(frame: NSRect(x: 0, y: 0, width: 500, height: 500))
view.autoenablesDefaultLighting = true
view.scene = scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
scene.rootNode.addChildNode(cameraNode)
XCPShowView("view", view)
// Make a pyramid to test on
let node = SCNNode(geometry: SCNPyramid(width: 1, height: 1, length: 1))
scene.rootNode.addChildNode(node)
node.physicsBody = SCNPhysicsBody.dynamicBody()
scene.physicsWorld.gravity = SCNVector3Zero // Don't fall off screen
// Rotate around the axis that looks into the screen
node.physicsBody?.applyTorque(SCNVector4(x: 0, y: 0, z: 1, w: 0.1), impulse: true)
// Wait a bit, then try to rotate around the y-axis
node.runAction(SCNAction.waitForDuration(10), completionHandler: {
var axis = SCNVector3(x: 0, y: 1, z: 0)
node.physicsBody?.applyTorque(SCNVector4(x: axis.x, y: axis.y, z: axis.z, w: 1), impulse: true)
})
The second rotation effectively spins the pyramid around the screen's y-axis, not the pyramid's y-axis -- the one that goes through the apex of the pyramid. As you noted, it's spinning around what was the pyramid's y-axis as of before the first rotation; i.e. the y-axis of the scene (which is unaffected by physics), not that of the presentation node (that was rotated through physics).
To fix it, insert the following line (after the one that starts with var axis):
axis = scene.rootNode.convertPosition(axis, fromNode: node.presentationNode())
The call to convertPosition:fromNode: says "give me a vector in scene coordinate space that's equivalent to this one in presentation-node space". When you apply a torque around the converted axis, it effectively converts back to the presentation node's space to simulate physics, so you see it spin around the axis you want.
Update: Had some coordinate spaces wrong, but the end result is pretty much the same.
Unfortunately the solution provided by rickster does not work for me :(
Trying to solve this conundrum I have created (what i believe to be) a very sub-standard solution (more a proof of concept). It involves creating (null) objects on the axis i am trying to find, then I use their position to find the vector aligned to the axes.
As I have a fairly complex scene, I am loading it from a COLLADA file. Within that file i have modelled a simple coordinate tripod: three orthogonal cylinders with cones on top (makes it easer to visualise what is going on).
I then constrain this tripod object to the object I am trying to apply torque to. This way I have objects that allow me to retrieve two points on the axes of the presentationNode of the object I am trying to apply torque to. I can then use these two points to determine the vector to apply the torque from.
// calculate orientation vector in the most unimaginative way possible
// retrieve axis tripod objects. We will be using these as guide objects.
// The tripod is constructed as a cylinder called "Xaxis" with a cone at the top.
// All loaded from an external COLLADA file.
SCNNode *XaxisRoot = [scene.rootNode childNodeWithName:#"XAxis" recursively:YES];
SCNNode *XaxisTip = [XaxisRoot childNodeWithName:#"Cone" recursively:NO];
// To devise the vector we will need two points. One is the root of our tripod,
// the other is at the tip. First, we get their positions. As they are constrained
// to the _rotatingNode, presentationNode.position is always the same .position
// because presentationNode returns position in relation to the parent node.
SCNVector3 XaxisRootPos = XaxisRoot.position;
SCNVector3 XaxisTipPos = XaxisTip.position;
// We then convert these two points into _rotatingNode coordinate space. This is
// the coordinate space applyTorque seems to be using.
XaxisRootPos = [_rotatingNode convertPosition:XaxisRootPos fromNode:_rotatingNode.presentationNode];
XaxisTipPos = [_rotatingNode convertPosition:XaxisTipPos fromNode:_rotatingNode.presentationNode];
// Now, we have two *points* in _rotatingNode coordinate space. One is at the center
// of our _rotatingNode, the other is somewhere along it's Xaxis. Subtracting them
// will give us the *vector* aligned to the x axis of our _rotatingNode
GLKVector3 rawXRotationAxes = GLKVector3Subtract(SCNVector3ToGLKVector3(XaxisRootPos), SCNVector3ToGLKVector3(XaxisTipPos));
// we now normalise this vector
GLKVector3 normalisedXRotationAxes = GLKVector3Normalize(rawXRotationAxes);
//finally we are able to apply toque reliably
[_rotatingNode.physicsBody applyTorque:SCNVector4Make(normalisedXRotationAxis.x,normalisedXRotationAxis.y,normalisedXRotationAxis.z, 500) impulse:YES];
As you can probably see, I am quite inexperienced in SceneKit, but even I can see that much easier/optimised solution does exits, but I am unable to find it :(
I recently had this same problem, of how to convert a torque from the local space of the object to the world space required by the applyTorque method. The problem with using the node's convertPosition:toNode and fromNodes methods, is that they are also applying the node's translation to the torque, so this will only work when the node is at 0,0,0. What these methods do is treat the SCNVector3 as if it's a vec4 with a w component of 1.0. We just want to apply the rotation, in other words, we want the w component of the vec4 to be 0. Unlike SceneKit, GLKit gives us 2 options for how we want our vec3s to be multiplied:
GLKMatrix4MultiplyVector3 where
The input vector is treated as it were a 4-component vector with a w-component of 0.0.
and GLKMatrix4MultiplyVector3WithTranslation where
The input vector is treated as it were a 4-component vector with a w-component of 1.0.
What we want here is the former, just the rotation, not the translation.
So, we could roundtrip to GLKit. To convert for instance the local x axis (1,0,0), eg a pitch rotation, to the global axis needed for apply torque, would look like this:
let local = GLKMatrix4MultiplyVector3(SCNMatrix4ToGLKMatrix4(node.presentationNode.worldTransform), GLKVector3(v: (1,0,0)))
node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
However, a more Swiftian approach would be to add a * operator for mat4 * vec3 which treats the vec3 like a vec4 with a 0.0 w component. Like this:
func * (left: SCNMatrix4, right: SCNVector3) -> SCNVector3 { //multiply mat4 by vec3 as if w is 0.0
return SCNVector3(
left.m11 * right.x + left.m21 * right.y + left.m31 * right.z,
left.m12 * right.x + left.m22 * right.y + left.m32 * right.z,
left.m13 * right.x + left.m23 * right.y + left.m33 * right.z
)
}
Although this operator makes an assumption about how we want our vec3s to be multiplied, my reasoning here is that as the convertPosition methods already treat w as 1, it would be redundant to have a * operator that also did this.
You could also add a mat4 * SCNVector4 operator that would let the user explicity choose whether or not they want w to be 0 or 1.
So, instead of having to roundtrip from SceneKit to GLKit, we can just write:
let local = node.presentationNode.worldTransform * SCNVector3(1,0,0)
node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
You can use this method to apply rotation on multiple axes with one applyTorque call. So say if you have stick input where you want x on the stick to be yaw (local yUp-axis) and y on the stick to be pitch (local x-axis), but with flight-sim style "down to pull back/ up", then you could set it to SCNVector3(input.y, -input.x, 0)