I have an ARSCNView that allows users to draw in 3d space. The drawing looks fine if we look at it from the camera angle where we drew it like in image 1. But when I look at it from another camera angle, it looks out of shape like in image 2. Of course, users would expect the ring to not change shape if they look at it from a different angle.
The following code is:
- Getting the touch point in 2d using a gesture recognizer
- Finding the corresponding 3d point using
hitTestmethod onsceneView - Asking to draw a line from the previous point to the current point.
Code
@objc func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
gestureRecognizer.minimumNumberOfTouches = 1
let results = self.sceneView.hitTest(gestureRecognizer.location(in: gestureRecognizer.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result = results.first else {
return
}
if gestureRecognizer.state == .began {
vector1 = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
} else if gestureRecognizer.state == .changed {
let vector2 = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
guard let vector1 = vector1 else {
self.vector1 = vector2
return
}
let line = lineFrom(vector: vector1, toVector: vector2)
let lineNode = SCNNode(geometry: line)
sceneView.scene.rootNode.addChildNode(lineNode)
self.vector1 = vector2
} else if gestureRecognizer.state == .ended {
vector1 = nil
}
}
I think I need to normalize the horizontal distance from the current device position to each point in the drawing. I tried to do this by changing this line:
let vector2 = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
to:
let vector2 = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, vector1.z - currentCameraPositionRelativeToOrigin.z - currentCameraRotation.z)
But this only made the problem worse. I would appreciate any pointers in the right direction.
Edit: Here is the code that draws the actual line segments:
class func lineThrough(points: [SCNVector3], width:Int = 20, closed: Bool = false, color: CGColor = UIColor.black.cgColor, mitter: Bool = false) -> SCNGeometry {
// Becouse we cannot use geometry shaders in metal, every point on the line has to be changed into 4 verticles
let vertices: [SCNVector3] = points.flatMap { p in [p, p, p, p] }
// Create Geometry Source object
let source = SCNGeometrySource(vertices: vertices)
// Create Geometry Element object
var indices = Array((0..<Int32(vertices.count)))
if (closed) {
indices += [0, 1]
}
let element = SCNGeometryElement(indices: indices, primitiveType: .triangleStrip)
// Prepare data for vertex shader
// Format is line width, number of points, should mitter be included, should line create closed loop
let lineData: [Int32] = [Int32(width), Int32(points.count), Int32(mitter ? 1 : 0), Int32(closed ? 1 : 0)]
let geometry = SCNGeometry(sources: [source], elements: [element])
geometry.setValue(Data(bytes: lineData, count: MemoryLayout<Int32>.size*lineData.count), forKeyPath: "lineData")
// map verticles into float3
let floatPoints = vertices.map { float3($0) }
geometry.setValue(NSData(bytes: floatPoints, length: MemoryLayout<float3>.size * floatPoints.count), forKeyPath: "vertices")
// map color into float
let colorFloat = color.components!.map { Float($0) }
geometry.setValue(NSData(bytes: colorFloat, length: MemoryLayout<simd_float1>.size * color.numberOfComponents), forKey: "color")
// Set the shader program
let program = SCNProgram()
program.fragmentFunctionName = "thickLinesFragment"
program.vertexFunctionName = "thickLinesVertex"
geometry.program = program
return geometry
}