Skip to content

Instantly share code, notes, and snippets.

@eugenebokhan
Created March 2, 2018 12:03
Show Gist options
  • Save eugenebokhan/13c1ebcbf88eea0a583ddee92057e083 to your computer and use it in GitHub Desktop.
Save eugenebokhan/13c1ebcbf88eea0a583ddee92057e083 to your computer and use it in GitHub Desktop.
ARSCNView extensions
import ARKit
extension ARSCNView {
func setup() {
antialiasingMode = .multisampling4X
automaticallyUpdatesLighting = false
preferredFramesPerSecond = 60
contentScaleFactor = 1.3
if let camera = pointOfView?.camera {
camera.wantsHDR = true
camera.wantsExposureAdaptation = true
camera.exposureOffset = -1
camera.minimumExposure = -1
camera.maximumExposure = 3
}
}
struct HitTestRay {
let origin: SCNVector3
let direction: SCNVector3
}
func hitTestRayFromScreenPos(_ point: CGPoint) -> HitTestRay? {
guard let frame = self.session.currentFrame else {
return nil
}
let cameraPos = SCNVector3.positionFromTransform(frame.camera.transform)
// Note: z: 1.0 will unproject() the screen position to the far clipping plane.
let positionVec = SCNVector3(x: Float(point.x), y: Float(point.y), z: 1.0)
let screenPosOnFarClippingPlane = self.unprojectPoint(positionVec)
var rayDirection = screenPosOnFarClippingPlane - cameraPos
rayDirection.normalize()
return HitTestRay(origin: cameraPos, direction: rayDirection)
}
func hitTestWithInfiniteHorizontalPlane(_ point: CGPoint, _ pointOnPlane: SCNVector3) -> SCNVector3? {
guard let ray = hitTestRayFromScreenPos(point) else {
return nil
}
// Do not intersect with planes above the camera or if the ray is almost parallel to the plane.
if ray.direction.y > -0.03 {
return nil
}
// Return the intersection of a ray from the camera through the screen position with a horizontal plane
// at height (Y axis).
return rayIntersectionWithHorizontalPlane(rayOrigin: ray.origin, direction: ray.direction, planeY: pointOnPlane.y)
}
func rayIntersectionWithHorizontalPlane(rayOrigin: SCNVector3, direction: SCNVector3, planeY: Float) -> SCNVector3? {
let direction = direction.normalized()
// Special case handling: Check if the ray is horizontal as well.
if direction.y == 0 {
if rayOrigin.y == planeY {
// The ray is horizontal and on the plane, thus all points on the ray intersect with the plane.
// Therefore we simply return the ray origin.
return rayOrigin
} else {
// The ray is parallel to the plane and never intersects.
return nil
}
}
// The distance from the ray's origin to the intersection point on the plane is:
// (pointOnPlane - rayOrigin) dot planeNormal
// --------------------------------------------
// direction dot planeNormal
// Since we know that horizontal planes have normal (0, 1, 0), we can simplify this to:
let dist = (planeY - rayOrigin.y) / direction.y
// Do not return intersections behind the ray's origin.
if dist < 0 {
return nil
}
// Return the intersection point.
return rayOrigin + (direction * dist)
}
struct FeatureHitTestResult {
let position: SCNVector3
let distanceToRayOrigin: Float
let featureHit: SCNVector3
let featureDistanceToHitResult: Float
}
func hitTestWithFeatures(_ point: CGPoint, coneOpeningAngleInDegrees: Float,
minDistance: Float = 0,
maxDistance: Float = Float.greatestFiniteMagnitude,
maxResults: Int = 1) -> [FeatureHitTestResult] {
var results = [FeatureHitTestResult]()
guard let features = self.session.currentFrame?.rawFeaturePoints else {
return results
}
guard let ray = hitTestRayFromScreenPos(point) else {
return results
}
let maxAngleInDeg = min(coneOpeningAngleInDegrees, 360) / 2
let maxAngle = ((maxAngleInDeg / 180) * Float.pi)
let points = features.__points
for i in 0...features.__count {
let feature = points.advanced(by: Int(i))
let featurePos = SCNVector3(feature.pointee)
let originToFeature = featurePos - ray.origin
let crossProduct = originToFeature.cross(ray.direction)
let featureDistanceFromResult = crossProduct.length()
let hitTestResult = ray.origin + (ray.direction * ray.direction.dot(originToFeature))
let hitTestResultDistance = (hitTestResult - ray.origin).length()
if hitTestResultDistance < minDistance || hitTestResultDistance > maxDistance {
// Skip this feature - it is too close or too far away.
continue
}
let originToFeatureNormalized = originToFeature.normalized()
let angleBetweenRayAndFeature = acos(ray.direction.dot(originToFeatureNormalized))
if angleBetweenRayAndFeature > maxAngle {
// Skip this feature - is is outside of the hit test cone.
continue
}
// All tests passed: Add the hit against this feature to the results.
results.append(FeatureHitTestResult(position: hitTestResult,
distanceToRayOrigin: hitTestResultDistance,
featureHit: featurePos,
featureDistanceToHitResult: featureDistanceFromResult))
}
// Sort the results by feature distance to the ray.
results = results.sorted(by: { (first, second) -> Bool in
return first.distanceToRayOrigin < second.distanceToRayOrigin
})
// Cap the list to maxResults.
var cappedResults = [FeatureHitTestResult]()
var i = 0
while i < maxResults && i < results.count {
cappedResults.append(results[i])
i += 1
}
return cappedResults
}
func hitTestWithFeatures(_ point: CGPoint) -> [FeatureHitTestResult] {
var results = [FeatureHitTestResult]()
guard let ray = hitTestRayFromScreenPos(point) else {
return results
}
if let result = self.hitTestFromOrigin(origin: ray.origin, direction: ray.direction) {
results.append(result)
}
return results
}
func hitTestFromOrigin(origin: SCNVector3, direction: SCNVector3) -> FeatureHitTestResult? {
guard let features = self.session.currentFrame?.rawFeaturePoints else {
return nil
}
let points = features.__points
// Determine the point from the whole point cloud which is closest to the hit test ray.
var closestFeaturePoint = origin
var minDistance = Float.greatestFiniteMagnitude
for i in 0...features.__count {
let feature = points.advanced(by: Int(i))
let featurePos = SCNVector3(feature.pointee)
let originVector = origin - featurePos
let crossProduct = originVector.cross(direction)
let featureDistanceFromResult = crossProduct.length()
if featureDistanceFromResult < minDistance {
closestFeaturePoint = featurePos
minDistance = featureDistanceFromResult
}
}
// Compute the point along the ray that is closest to the selected feature.
let originToFeature = closestFeaturePoint - origin
let hitTestResult = origin + (direction * direction.dot(originToFeature))
let hitTestResultDistance = (hitTestResult - origin).length()
return FeatureHitTestResult(position: hitTestResult,
distanceToRayOrigin: hitTestResultDistance,
featureHit: closestFeaturePoint,
featureDistanceToHitResult: minDistance)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment