SCNNode direction issue - ios

I put a SCNNode to a scene. I want to provide proper rotation in space, because this node is a pyramid. I want Z axis to be pointed to V2 point, and X axis to be pointed to V1 point (these V2 and V1 point are calculated dynamically, and of course in this case the angle between axis will be 90 degrees, because I calculate them properly).
The problem: I can't point X axis, because SCNLookAtConstraint(target: nodeWithV2) points only Z axis, and what I see is that Z axis is OK, but X axis is always random, that's why my Pyramid orientation is always wrong. How can I point X axis?
Here is my code:
let pyramidGeometry = SCNPyramid(width: 0.1, height: 0.2, length: 0.001)
pyramidGeometry.firstMaterial?.diffuse.contents = UIColor.white
pyramidGeometry.firstMaterial?.lightingModel = .constant
pyramidGeometry.firstMaterial?.isDoubleSided = true
let nodePyramid = SCNNode(geometry: pyramidGeometry)
nodePyramid.position = SCNVector3(2, 1, 2)
parent.addChildNode(nodePyramid)
let nodeZToLookAt = SCNNode()
nodeZToLookAt.position = v2
parent.addChildNode(nodeZToLookAt)
let constraint = SCNLookAtConstraint(target: nodeZToLookAt)
nodePyramid.constraints = [constraint]
But that only sets the direction for Z axis, that's why X axis is always random, so the rotation is always random. How can I set X axis direction to my point V1?

starting iOS 11 SCNLookAtConstraint has a localFront property that allows you to change which axis is used to orientate the constrained node.

Related

SCNNode Z-rotation axis stays constant, while X and Y axes change when node is rotated

I have a node that the user can rotate via a pan gesture. The user can select either X, Y, or Z axis and pan, and the node will rotate around that axis.
The Issue: The node's front is facing the camera. Let's say the user pans to the right and rotates the node around the Y axis. The node's front is now facing the right. If the user switches to the X axis and pans down, the node's front will rotate downward (or clockwise from the user's perspective) from it's initial right-facing orientation. This is desired behavior. The problem arises when the user switches to the Z rotation. If the user switches to Z axis rotation and pans right, the node will rotate down (clockwise from the user's perspective).
Essentially, the Z axis of the node is always constant, never moving from it's initial orientation, but the X and Y axes do change, affected by other axes' rotations.
Can anyone explain what's causing this?
Below is the code I'm using to rotate the node:
let translation = sender.translation(in: sceneView)
var newAngleX = Float(translation.y)*Float((Double.pi)/180.0)
var newAngleY = Float(translation.x)*Float((Double.pi)/180.0)
var newAngleZ = Float(translation.x)*Float((Double.pi)/180.0)
if axisSelected == "x" {
newAngleX += currentAngleX
node.eulerAngles.x = newAngleX
if(sender.state == .ended) {
currentAngleX = newAngleX
}
}
if axisSelected == "y" {
newAngleY += currentAngleY
node.eulerAngles.y = newAngleY
if(sender.state == .ended) {
currentAngleY = newAngleY
}
}
if axisSelected == "z" {
newAngleZ += currentAngleZ
node.eulerAngles.z = newAngleZ
if(sender.state == .ended) {
currentAngleZ = newAngleZ
}
}
As I wrote earlier in your post it's a Gimbal Lock issue. Gimbal Lock is the loss of one DoF in a three-dimensional mechanism. There are two variables in SceneKit: eulerAngles (intrinsic euler angles, expressed in radians, represent a sequence of 3 rotations about the x-y-z axis with the following order: Z-Y-X or roll, yaw, pitch) and orientation (node’s orientation, expressed as quaternion).
To get rid of gimbal lock in ARKit and SceneKit you need to use unit quaternions, whose components satisfy the equation:
(x * x) + (y * y) + (z * z) + (w * w) == 1
For applying quaternions correctly you need to use the fourth component of this structure too (w). Quaternions are expressed in SCNVector4, where x-y-z values are normalized components, and the w field contains the rotation angle, in radians, or torque magnitude, in newton-meters).
Gimbal Lock occurs when the axes (of two of the three gimbals) are driven into a parallel configuration.

Scenekit - multiple rotation of SCNNode and direction of axes

I have a SCNNode which i keep on rotating with pan gesture by 90deg increments. The first few rotations are working as intended, by with a scenario where the nodes axes rotate in the oposite direction, the rotation is executed in wrong direction. How can i determine the orientation of the axes after each rotation?
Scenario (using a cube for simplicity):
i rotate the cube 90deg along Y axis. Y points still up, X now points to the camera, Z points right
i rotate the cube again 90deg along Y. X now points left, Z to the camera
PROBLEM - i now try to rotate 90deg along X axis. Because X got rotated 180 degrees, the rotation is now reversed.
How can i understand when to rotate (-1,0,0) and when (1,0,0) ?
I'm quite new to the world of 3D math, i hope i explained my issue correctly.
After further research i realised i chose completely wrong approach. The way to achieve what i want was to rotate the node using the axes of the rootNode - this way i don't need to worry about local axes of my node.
EDIT: updated code based on Xartec's suggestions
let rotation = SCNMatrix4MakeRotation(angle, x, y, Float(0))
let newTransform = SCNMatrix4Mult(bricksNode.transform, rotation)
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = bricksNode.transform
animation.toValue = scnScene.rootNode.convertTransform(newTransform,from: nil)
animation.duration = 0.5
bricksNode.addAnimation(animation, forKey: nil)

Translate 3D position taken from CMMotionManager() to set angle on 2D CG Coordinate System

I don't know if this is basic math I have to compute, or my inexperience with the pitch, roll, and yaw values. At the moment I have an image object that moves based on my accelerometer values.
//Move the ball based on accelerator values
delta.x = CGFloat(acceleration.x * 10)
delta.y = CGFloat(acceleration.y * 10)
ball.center = CGPointMake(ball.center.x + delta.x, ball.center.y + delta.y)
I can calculate the pitch through the attitude and get the angle. What I want to do is line up my "ball" in the center of the screen only when the angle of the phone is a certain angle, lets say 45 degrees. How can I move my ball so that it lines up in the center based on specific angles given?
Your screen height is Η pixels.
Your screen width is W pixels.
The horizontal centre of the screen is x = W / 2
I'm assuming from your question you want the ball centre to vary between the top (x, 0) when the screen is flat and bottom (x, H) when the screen is vertical.
If the angle of your phone θ varies between 0 and π, then y = θ / π * H
ball.center = CGPoint(x: W / 2, y: θ / π * H)
All you need is the trig to work out θ based on the gyro readings

Apply rotation around axis defined by touched point

I have an object displayed using OpenGL ES on an iPad. The model is defined by vertices, normals and indexes to vertices. The origin of the model is 0,0,0. Using UIGestureRecognizer I can detect various gestures - two-fingered swipe horizontally for rotation about y, vertically for rotation about x. Two-fingered rotate gesture for rotation about y. Pan to move the model around. Pinch/zoom gesture to scale. I want the viewer to be able to manipulate the model to see (for example) the reverse of the model or the whole thing at once.
The basic strategy comes from Ray Wenderlich's tutorial but I have rewritten this in Swift.
I understand quaternions to be a vector and an angle. The vectors up, right and front represent the three axes:
front = GLKVector3Make(0.0, 0.0, 1.0)
right = GLKVector3Make(1.0, 0.0, 0.0)
up = GLKVector3Make(0.0, 1.0, 0.0)
so the quaternion apples a rotation around each of the three axes (though only one of dx, dy, dz has a value, decided by the gesture recognizer.)
func rotate(rotation : GLKVector3, multiplier : Float) {
let dx = rotation.x - rotationStart.x
let dy = rotation.y - rotationStart.y
let dz = rotation.z - rotationStart.z
rotationStart = GLKVector3Make(rotation.x, rotation.y, rotation.z)
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dx * multiplier, up), rotationEnd)
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dy * multiplier, right), rotationEnd)
rotationEnd = GLKQuaternionMultiply((GLKQuaternionMakeWithAngleAndVector3Axis(-dz, front)), rotationEnd)
state = .Rotation
}
Drawing uses the modelViewMatrix, calculated by the following function:
func modelViewMatrix() -> GLKMatrix4 {
var modelViewMatrix = GLKMatrix4Identity
// translation and zoom
modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, translationEnd.x, translationEnd.y, -initialDepth);
// rotation
let quaternionMatrix = GLKMatrix4MakeWithQuaternion(rotationEnd)
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, quaternionMatrix)
// scale
modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, scaleEnd, scaleEnd, scaleEnd);
// rotation
return modelViewMatrix
}
And mostly this works. However everything is relative to the origin.
If the model is rotated then the pivot is always an axis passing through the origin - if zoomed in looking at the end of the model away from the origin and then rotating, the model can rapidly swing out of view. If the model is scaled then the origin is always the fixed point with the model growing larger or smaller - if the origin is off-screen and scale is reduced the model can disappear from view as it collapses toward the origin...
What should happen is that whatever the current view, the model rotates or scales relative to the current view. For a rotation around the y axis that would mean defining the y axis around which the rotation occurs as passing vertically through the middle of the current view. For a scale operation the fixed point of the model would be in the centre of the screen with the model shrinking toward or growing outward from that point.
I know that in 2D the solution is always to translate to the origin, apply rotation and then apply the inverse of the first translation. I don't see why this should be different in 3D, but I cannot find any example doing this with quaternions only matrices. I have tried to apply a translation and its inverse around the rotation but nothing has an effect.
So I tried to do this in the rotate function:
let xTranslation : Float = 300.0
let yTranslation : Float = 300.0
let translation = GLKMatrix4Translate(GLKMatrix4Identity, xTranslation, yTranslation, -initialDepth);
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithMatrix4(translation) , rotationEnd)
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dx * multiplier, up), rotationEnd)
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithAngleAndVector3Axis(dy * multiplier, right), rotationEnd)
rotationEnd = GLKQuaternionMultiply((GLKQuaternionMakeWithAngleAndVector3Axis(-dz, front)), rotationEnd)
// inverse translation
let inverseTranslation = GLKMatrix4Translate(GLKMatrix4Identity, -xTranslation, -yTranslation, -initialDepth);
rotationEnd = GLKQuaternionMultiply(GLKQuaternionMakeWithMatrix4(inverseTranslation) , rotationEnd)
The translation is 300,300 but there is no effect at all, it still pivots around where I know the origin to be. I've searched a long time for sample code and not found any.
The modelViewMatrix is applied in update() with:
effect?.transform.modelviewMatrix = modelViewMatrix
I could also cheat by adjusting all of the values in the model so that 0,0,0 falls at a central point - but that would still be a fixed origin and would be only marginally better.
The problem is in the last operation you made, you should swap the inverseTranslation with rotationEnd :
rotationEnd = GLKQuaternionMultiply(rotationEnd, GLKQuaternionMakeWithMatrix4(inverseTranslation))
And I think the partial rotation(dx, dy, dz) should follow the same rule.
In fact, if you want to change the pivot, this is how your matrix multiplication should be done:
modelMatrix = translationMatrix * rotationMatrix * inverse(translationMatrix)
and the result in homogeneous coordinates will be calculated as follows:
newPoint = translationMatrix * rotationMatrix * inverse(translationMatrix) * v4(x,y,z,1)
Example
This is a 2D test example that you can run in a playground.
let v4 = GLKVector4Make(1, 0, 0, 1) // Point A
let T = GLKMatrix4Translate(GLKMatrix4Identity, 1, 2, 0);
let rot = GLKMatrix4MakeWithQuaternion(GLKQuaternionMakeWithAngleAndVector3Axis(Float(M_PI)*0.5, GLKVector3Make(0, 0, 1))) //rotate by PI/2 around the z axis.
let invT = GLKMatrix4Translate(GLKMatrix4Identity, -1, -2, 0);
let partModelMat = GLKMatrix4Multiply(T, rot)
let modelMat = GLKMatrix4Multiply(partModelMat, invT) //The parameters were swapped in your code
//and the result would the rot matrix, since T*invT will be identity
var v4r = GLKMatrix4MultiplyVector4(modelMat, v4) //ModelMatrix multiplication with pointA
print(v4r.v) //(3,2,0,1)
//Step by step multiplication using the relation described above
v4r = GLKMatrix4MultiplyVector4(invT, v4)
v4r = GLKMatrix4MultiplyVector4(rot, v4r)
v4r = GLKMatrix4MultiplyVector4(T, v4r)
print(v4r.v) //(3,2,0,1)
As for the scale, if I understand correctly what you want, I would recommend to do it like it's done here: https://gamedev.stackexchange.com/questions/61473/combining-rotation-scaling-around-a-pivot-with-translation-into-a-matrix

How to get coordinate on the chart

I have used following way to get x-axis and y-axis co-ordinate. But I'm unable to get y axis co-ordinates.
**/*X axis max*/**
double xMaxAxisBottom = m_Chart.GetAxis().GetBottom().GetMaximum();// try to get x axis
double xMaxAxisBottomPixelPos = m_Chart.GetAxis().GetBottom().CalcXPosValue(xMaxAxisBottom); // Here trying to position based on x-axis co-ordinate
**/*X axis min*/**
double xMinAxisBottom = m_reschedChart.GetAxis().GetBottom().GetMinimum();//// try to get x axis minimum
double xMinAxisBottomPixelPos = m_Chart.GetAxis().GetBottom().MinXValue();
**/*Y axis max*/**
double xMaxAxisLeft = m_reschedChart.GetAxis().GetLeft().GetMaximum();
double xMaxAxisLeftPixelPos = m_reschedChart.GetAxis().GetLeft().MaxXValue();
**/*Y axis min*/**
double xMinAxisLeft = m_Chart.GetAxis().GetLeft().GetMinimum();
double xMinAxisLeftPixelPos = m_Chart.GetAxis().GetLeft().MinXValue();
**/*X axis length*/**
double xAxisBottomLen = m_Chart.GetAxis().GetBottom().GetEndPosition() - m_Chart.GetAxis().GetBottom().GetStartPosition();
double xAxisBottomLenPixelPos = m_Chart.GetAxis().GetBottom().CalcXPosValue(xAxisBottomLen);
**/*Y axis length*/**
double yAxisLeftLen = m_Chart.GetAxis().GetLeft().GetEndPosition() - m_Chart.GetAxis().GetLeft().GetStartPosition();
double xAxisLeftLenPixelPos = m_Chart.GetAxis().GetBottom().CalcXPosValue(yAxisLeftLen);
**/*X origin*/**
double dXstartPos = m_Chart.GetAxis().GetBottom().GetStartPosition();
double dXstartPixelPos = m_reschedChart.GetAxis().GetBottom().CalcXPosValue(dXstartPos);
**/*Y origin*/**
double dYStartPos = m_Chart.GetAxis().GetLeft().GetStartPosition();
double dYStartPixelPos = m_Chart.GetAxis().GetLeft().CalcXPosValue(dYStartPos);
let me know if i am making any mistake to find the co-ordinates.
I wanted to find below mention co-ordinated using above code.
1 X axis max
2 X axis min
3 Y axis max
4 Y axis min
5 X axis length
6 Y axis length
7 X origin
8 Y origin
9 Label font size
Please let me know your view.
Thanks
The chart needs to be drawn to use these methods. They need some internal properties to be initialized to work as expected.
You can force a chart repaint before calling them with:
m_Chart.GetEnvironment().InternalRepaint();
EDIT:
Since you seem to be calling these functions at OnAfterDraw event, you don't need to force a chart repaint. However, I'd suggest you some modifications in your code.
I see you are using m_Chart and also m_reschedChart. Make sure you are using the correct TChart variable.
Your variables start with x and y but thay also include Bottom or Left depending on the axis they refer. This is redundant and increments the chances to write a mistake (ie xMaxAxisLeft).
CalcXPosValue has to be used with Horizontal axes and CalcYPosValue with Vertical axes. So you shouldn't call GetLeft().CalcXPosValue.
CalcXPosValue and CalcYPosValue are functions to convert axis values to screen pixels.
MinXValue and MaxXValue are to be used with Horizontal Axes while MinYValue and MaxYValue are to be used with Vertical Axes.
GetMinimum returns the same than MinXValue/MinYValue, and GetMaximum returns the same than MaxXValue/MaxYValue. All these functions return Axis values, not screen pixels.
GetStartPosition and EndStartPosition are thought to modify the Axis length and by default they use percentages as explained here, so GetStartPosition - EndStartPosition is always zero. And I think CalcXPosValue(GetStartPosition - EndStartPosition) is conceptually wrong too. Note IStartPos and IEndPos give you the Start and End positions in pixels. See the TeeChart ActiveX Tutorials here.
Find below the modified code I suggest you:
**/*X axis max*/**
double maxAxisBottom = m_Chart.GetAxis().GetBottom().GetMaximum();// try to get x axis
double maxAxisBottomPixelPos = m_Chart.GetAxis().GetBottom().CalcXPosValue(xMaxAxisBottom); // Here trying to position based on x-axis co-ordinate
**/*X axis min*/**
double minAxisBottom = m_Chart.GetAxis().GetBottom().GetMinimum();//// try to get x axis minimum
double minAxisBottomPixelPos = m_Chart.GetAxis().GetBottom().CalcXPosValue(minAxisBottom);
**/*Y axis max*/**
double maxAxisLeft = m_Chart.GetAxis().GetLeft().GetMaximum();
double maxAxisLeftPixelPos = m_Chart.GetAxis().GetLeft().CalcYPosValue(maxAxisLeft);
**/*Y axis min*/**
double minAxisLeft = m_Chart.GetAxis().GetLeft().GetMinimum();
double minAxisLeftPixelPos = m_Chart.GetAxis().GetLeft().CalcYPosValue(minAxisLeft);
Now you already know the position of the four squares in screen pixels so you can ie draw a rectangle using them to check it:
m_Chart.getCanvas().Rectangle(minAxisBottomPixelPos, minAxisLeftPixelPos, maxAxisBottomPixelPos, maxAxisLeftPixelPos);
If you also want or need the sizes of the axes in pixels, you can do:
**/*X axis length*/**
double axisBottomLenPixelPos = m_Chart.GetAxis().GetBottom().GetIEndPos() - m_Chart.GetAxis().GetBottom().GetIStartPos();
**/*Y axis length*/**
double axisLeftLenPixelPos = m_Chart.GetAxis().GetLeft().GetIEndPos() - m_Chart.GetAxis().GetLeft().GetIStartPos();
And you can check they are correctly calculated:
m_Chart.getCanvas().Rectangle minAxisBottomPixelPos, maxAxisLeftPixelPos, minAxisBottomPixelPos + axisBottomLenPixelPos, maxAxisLeftPixelPos + axisLeftLenPixelPos

Resources