Kevin Goedecke Kevin Goedecke - 2 months ago 17
iOS Question

Creating SCNCylinder in SceneKit at touch position isn't accurate

I'm trying to create a flat cylinder in SceneKit using

SCNCylinder
. I want the cylinder to be placed in the scene at the position where the user taps the screen.

My current approach works, but for some reason the cylinder is not accurately placed at the touch position. Depending on the part of the screen the cylinder sometimes is in the middle of the touch position and sometimes off by a significant amount. I hope the screenshot illustrates the problem well enough.

Offset when creating SCNCylinder at screenpos

I currently have a
SCNSphere
in which the camera is located. By hittesting the screen touch point with the sphere I retrieve a ray towards the hit-test. I then take the normal vector of the ray and position the Cylinder along the vector multiplied by 6.

Does anyone have an idea what the issue with this approach is and why I'm experiencing this offset behavior?

This how I currently create the
SCNCylinder
:

- (IBAction)longPressGesture:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
CGPoint location = [sender locationInView:self.sceneView];
NSArray *hitTestResult = [self.sceneView hitTest:location options:nil];

if (hitTestResult.count == 1) {
SCNHitTestResult *sphereHit = hitTestResult.firstObject;
// Get ray coordinates from local camera position
SCNVector3 localCoordinates = sphereHit.worldNormal;
localCoordinates = SCNVector3Make(localCoordinates.x * 6, localCoordinates.y * 6, localCoordinates.z * 6);
[self addCylinder:SCNVector3Make(localCoordinates.x, localCoordinates.y, localCoordinates.z)];
}
}
}

- (void)addCylinder:(SCNVector3)position {
SCNCylinder *cylinder = [SCNCylinder cylinderWithRadius:0.5 height:0.01];
SCNNode *cylinderNode = [SCNNode nodeWithGeometry:cylinder];

// Create LookAt Contstraint
NSMutableArray *constraints = [NSMutableArray new];
SCNLookAtConstraint *lookAtCameraConstraint = [SCNLookAtConstraint lookAtConstraintWithTarget:cameraNode];
lookAtCameraConstraint.gimbalLockEnabled = YES;
[constraints addObject:lookAtCameraConstraint];

// Turn 90° Constraint
SCNTransformConstraint *turnConstraint = [SCNTransformConstraint transformConstraintInWorldSpace:NO withBlock:^SCNMatrix4(SCNNode * _Nonnull node, SCNMatrix4 transform) {
transform = SCNMatrix4Mult(SCNMatrix4MakeRotation(M_PI_2, 1, 0, 0), transform);
return transform;
}];
[constraints addObject:turnConstraint];

cylinderNode.constraints = constraints;

cylinderNode.position = position;

SCNNode *rootNode = self.sceneView.scene.rootNode;
[rootNode addChildNode:cylinderNode];
}

Answer

Every SCNSphere in SceneKit is created out of polygons. The number of polygons and thus the granularity a SCNSphere mesh is determined by the segmentCount property.

Documentation of SCNSphere

By default the segmentCount value is set to 48, which is not very finely grained. Performing a hitTest: on a SCNSphere with a low segmentCount will result in retrieving polygons that have an offset compared to the actual touch point. By increasing the segmentCount (e.g. to 96) the segments in horizontal and vertical direction are increased and the offset will reduce.

Keep in mind that increasing the segmentCount will have an impact on the performance.