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...
Related
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 have a camera in the center of SceneKit scene at position(0,0,0)
I'm rotation the camera around the y-axis to show different parts of the scene.
I want to show arrows at the left/right side of my screen to show in which direction the user has to move to view a target object.
How can I calculate if a ScnNode is positioned at the left of right side of my view direction?
I know:
position and rotation of the camera node
position of the target node
Calculate the vector to the node relative to the camera. Calculate the vector by subtracting the camera position from the node position
Use atan2 to convert the vector into an angle. The Apple docs are bit thin on explanation. There's a tutorial on raywenderlich.com.
E.g.
let v = CGPoint(x: nodePosition.x - cameraPosition.x, y: nodePosition.y - cameraPosition.y);
let a = atan2(v.y, v.x) // Note: order of arguments
The angle is in radians, and will range between -π and π (-180 to 180 degrees). If the value is negative the node is to the left of the camera vector, positive values mean the node is to the right, and zero means the node is straight ahead.
Conveniently, you can use this angle as-is to actually perform the rotation if you want. Just increment the camera angle by some fraction of the angle to the node.
E.g.
cameraRotation += a * 0.1
Caveats:
"Forward" or "Up" is not necessarily going to be the same as your internal coordinate system. Usually 0 is to the right, so if you want to show an arrow or other UI you may need to add or subtract an amount from the angle (usually -(π/2) to rotate a quarter turn).
The angle will always only be between -π and +π. If the node happens to fall directly behind the camera (exactly 180 degrees) you will get either -π or +π, and you might see the angle jump between these two extremes as the node cross over this discontinuity. You sometimes see the side effects of this in games where a nav arrow will point to a target then suddenly flick to the opposite side of the screen. There are various ways to handle this. One easy way is to use a low pass filter on the angle (ie accumulate multiple samples over time).
There is an easier way with projectPoint funcion witch show the image as 2d element in your view and based on where it is you can locate where the user should look
func getIfHeIsLookingNew(sceneWidth: CGFloat, nodePosition: SCNVector3){
if(nodePosition.z < 1){
if(nodePosition.x > (Float(sceneWidth))){
print("Look Right")
}else if(nodePosition.x < 0){
print("Look Left")
}else{
print("Correct")
return
}
}else if(nodePosition.x < 0){
print("Look Right")
}else{
print("Look Left")
}
}
And use it like below
let sceneWidth:CGFloat = self.sceneView.frame.width
getIfHeIsLookingNew(sceneWidth: sceneWidth, nodePosition: self.sceneView.projectPoint(targetNode.position))
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 essentially want the "sprites" to collide when they stick together. However, I don't want the "joint" to be rigid; I essentially want the sprites to be able to move around as long as they are in contact with each other. Imagine two circles connected, and you can move one circle around the other, as long as it remains in contact.
I found this question: How to make one body stick to another moving object in SpriteKit and a lot of other resources that explain how to make sprites stick upon collision, but they all use SKJoints, which are rigid are not really flexible.
I guess another way to phrase it would be to say that I want the sprites to stick, but I want them to be able to "slide" on each other.
Well, I can think of one workaround, but this wouldn't work with non-normal polygons.
Sticking (pun unintended) with your circles example, what if you lock the position of the circle?
let circle1 = center circle
let circle2 = movable circle
Knowing the width of both circles, you can place in the update function that the position should be exactly the distance of:
((circle1.frame.width / 2) + (circle2.frame.width / 2))
If you're up to it, here's some code to help you on your way.
override func update(currentTime: CFTimeInterval) {
{
let distance = hypotf(Float(circle1.position.x - circle2.position.x), Float(circle1.position.y - circle2.position.y))
//calculate circle distances from each other
let radius = ((circle1.frame.width / 2) + (circle2.frame.width / 2))
//distance of circle positions
if distance != radius
{
//if distance is less or more than radius
let pointA = circle1.position
let pointB = circle2.position
let pointC = CGPointMake(pointB.x + 2, pointB.y)
let angle_ab = atan2(pointA.y - pointB.y, pointA.x - pointB.x)
let angle_cb = atan2(pointC.y - pointB.y, pointC.x - pointB.x)
let angle_abc = angle_ab - angle_cb
//get angle of circles from each other using atan2
let vectorx = cos(angle_abc)
let vectory = sin(angle_abc)
//convert angle into vectors
let x = circle1.position.x + radius * vectorx
let y = circle1.position.y + radius * vectory
//get new coordinates from vector, radius and center circle position
circle2.position = CGPointMake(x, y)
//set new position
}
}
Well you need to write code to make sure the movable circle, is well movable.
But, this should work.
I haven't tested this yet though, and I haven't even learned geometry let alone trig in school yet.
If I'm reading your question as you intended it, you can still use joints- just create actions with Inverse Kinematic constraints that allow rotation and translation around the contacting circles' joint.
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKAction_Ref/index.html#//apple_ref/doc/uid/TP40013017-CH1-SW72
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.