Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift Styling #29

Merged
merged 3 commits into from
Feb 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/workflows/swift-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ jobs:
- uses: actions/checkout@v1
- name: Build Package
run: |
swift package generate-xcodeproj
xcodebuild clean build -project $PROJECT -scheme $SCHEME -sdk $SDK CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
sudo xcode-select --switch $DEVELOPER_DIR
xcodebuild -scheme $SCHEME -destination $DESTINATION | xcpretty
env:
DEVELOPER_DIR: /Applications/Xcode_12.app/Contents/Developer
PROJECT: FocusEntity.xcodeproj
SCHEME: FocusEntity-Package
SDK: iphoneos
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
SCHEME: FocusEntity
DESTINATION: generic/platform=iOS
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// swift-tools-version:5.0
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "FocusEntity",
platforms: [.iOS("13.0")],
platforms: [.iOS(.v13), .macOS(.v10_15)],
products: [
.library(name: "FocusEntity", targets: ["FocusEntity"])
],
Expand Down
284 changes: 142 additions & 142 deletions Sources/FocusEntity/FocusEntity+Alignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,162 +13,162 @@ import Combine

extension FocusEntity {

// MARK: Helper Methods

/// Update the position of the focus square.
internal func updatePosition() {
// Average using several most recent positions.
recentFocusEntityPositions = Array(recentFocusEntityPositions.suffix(10))

// Move to average of recent positions to avoid jitter.
let average = recentFocusEntityPositions.reduce(
SIMD3<Float>.zero, { $0 + $1 }
) / Float(recentFocusEntityPositions.count)
self.position = average
}

/// Update the transform of the focus square to be aligned with the camera.
internal func updateTransform(raycastResult: ARRaycastResult) {
self.updatePosition()

if state != .initializing {
updateAlignment(for: raycastResult)
// MARK: Helper Methods

/// Update the position of the focus square.
internal func updatePosition() {
// Average using several most recent positions.
recentFocusEntityPositions = Array(recentFocusEntityPositions.suffix(10))

// Move to average of recent positions to avoid jitter.
let average = recentFocusEntityPositions.reduce(
SIMD3<Float>.zero, { $0 + $1 }
) / Float(recentFocusEntityPositions.count)
self.position = average
}
}

internal func updateAlignment(for raycastResult: ARRaycastResult) {

var targetAlignment = raycastResult.worldTransform.orientation

// Determine current alignment
var alignment: ARPlaneAnchor.Alignment?
if let planeAnchor = raycastResult.anchor as? ARPlaneAnchor {
alignment = planeAnchor.alignment
// Catching case when looking at ceiling
if targetAlignment.act([0, 1, 0]).y < -0.9 {
targetAlignment *= simd_quatf(angle: .pi, axis: [0, 1, 0])
}
} else if raycastResult.targetAlignment == .horizontal {
alignment = .horizontal
} else if raycastResult.targetAlignment == .vertical {
alignment = .vertical

/// Update the transform of the focus square to be aligned with the camera.
internal func updateTransform(raycastResult: ARRaycastResult) {
self.updatePosition()

if state != .initializing {
updateAlignment(for: raycastResult)
}
}

// add to list of recent alignments
if alignment != nil {
self.recentFocusEntityAlignments.append(alignment!)
internal func updateAlignment(for raycastResult: ARRaycastResult) {

var targetAlignment = raycastResult.worldTransform.orientation

// Determine current alignment
var alignment: ARPlaneAnchor.Alignment?
if let planeAnchor = raycastResult.anchor as? ARPlaneAnchor {
alignment = planeAnchor.alignment
// Catching case when looking at ceiling
if targetAlignment.act([0, 1, 0]).y < -0.9 {
targetAlignment *= simd_quatf(angle: .pi, axis: [0, 1, 0])
}
} else if raycastResult.targetAlignment == .horizontal {
alignment = .horizontal
} else if raycastResult.targetAlignment == .vertical {
alignment = .vertical
}

// add to list of recent alignments
if alignment != nil {
self.recentFocusEntityAlignments.append(alignment!)
}

// Average using several most recent alignments.
self.recentFocusEntityAlignments = Array(self.recentFocusEntityAlignments.suffix(20))

let alignCount = self.recentFocusEntityAlignments.count
let horizontalHistory = recentFocusEntityAlignments.filter({ $0 == .horizontal }).count
let verticalHistory = recentFocusEntityAlignments.filter({ $0 == .vertical }).count

// Alignment is same as most of the history - change it
if alignment == .horizontal && horizontalHistory > alignCount * 3/4 ||
alignment == .vertical && verticalHistory > alignCount / 2 ||
raycastResult.anchor is ARPlaneAnchor {
if alignment != self.currentAlignment ||
(alignment == .vertical && self.shouldContinueAlignAnim(to: targetAlignment)
) {
isChangingAlignment = true
self.currentAlignment = alignment
}
} else {
// Alignment is different than most of the history - ignore it
return
}

// Change the focus entity's alignment
if isChangingAlignment {
// Uses interpolation.
// Needs to be called on every frame that the animation is desired, Not just the first frame.
performAlignmentAnimation(to: targetAlignment)
} else {
orientation = targetAlignment
}
}

// Average using several most recent alignments.
self.recentFocusEntityAlignments = Array(self.recentFocusEntityAlignments.suffix(20))
internal func normalize(_ angle: Float, forMinimalRotationTo ref: Float) -> Float {
// Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal
var normalized = angle
while abs(normalized - ref) > .pi / 4 {
if angle > ref {
normalized -= .pi / 2
} else {
normalized += .pi / 2
}
}
return normalized
}

let alignCount = self.recentFocusEntityAlignments.count
let horizontalHistory = recentFocusEntityAlignments.filter({ $0 == .horizontal }).count
let verticalHistory = recentFocusEntityAlignments.filter({ $0 == .vertical }).count
internal func getCamVector() -> (position: SIMD3<Float>, direciton: SIMD3<Float>)? {
guard let camTransform = self.arView?.cameraTransform else {
return nil
}
let camDirection = camTransform.matrix.columns.2
return (camTransform.translation, -[camDirection.x, camDirection.y, camDirection.z])
}

// Alignment is same as most of the history - change it
if alignment == .horizontal && horizontalHistory > alignCount * 3/4 ||
alignment == .vertical && verticalHistory > alignCount / 2 ||
raycastResult.anchor is ARPlaneAnchor {
if alignment != self.currentAlignment ||
(alignment == .vertical && self.shouldContinueAlignAnim(to: targetAlignment)
/// - Parameters:
/// - Returns: ARRaycastResult if an existing plane geometry or an estimated plane are found, otherwise nil.
internal func smartRaycast() -> ARRaycastResult? {
// Perform the hit test.
guard let (camPos, camDir) = self.getCamVector() else {
return nil
}
let rcQuery = ARRaycastQuery(
origin: camPos, direction: camDir,
allowing: self.allowedRaycast, alignment: .any
)
let results = self.arView?.session.raycast(rcQuery) ?? []

// 1. Check for a result on an existing plane using geometry.
if let existingPlaneUsingGeometryResult = results.first(
where: { $0.target == .existingPlaneGeometry }
) {
isChangingAlignment = true
self.currentAlignment = alignment
}
} else {
// Alignment is different than most of the history - ignore it
return
}
return existingPlaneUsingGeometryResult
}

// Change the focus entity's alignment
if isChangingAlignment {
// Uses interpolation.
// Needs to be called on every frame that the animation is desired, Not just the first frame.
performAlignmentAnimation(to: targetAlignment)
} else {
orientation = targetAlignment
}
}

internal func normalize(_ angle: Float, forMinimalRotationTo ref: Float) -> Float {
// Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal
var normalized = angle
while abs(normalized - ref) > .pi / 4 {
if angle > ref {
normalized -= .pi / 2
} else {
normalized += .pi / 2
}
// 2. As a fallback, check for a result on estimated planes.
return results.first(where: { $0.target == .estimatedPlane })
}
return normalized
}

internal func getCamVector() -> (position: SIMD3<Float>, direciton: SIMD3<Float>)? {
guard let camTransform = self.arView?.cameraTransform else {
return nil
}
let camDirection = camTransform.matrix.columns.2
return (camTransform.translation, -[camDirection.x, camDirection.y, camDirection.z])
}

/// - Parameters:
/// - Returns: ARRaycastResult if an existing plane geometry or an estimated plane are found, otherwise nil.
internal func smartRaycast() -> ARRaycastResult? {
// Perform the hit test.
guard let (camPos, camDir) = self.getCamVector() else {
return nil
}
let rcQuery = ARRaycastQuery(
origin: camPos, direction: camDir,
allowing: self.allowedRaycast, alignment: .any
)
let results = self.arView?.session.raycast(rcQuery) ?? []

// 1. Check for a result on an existing plane using geometry.
if let existingPlaneUsingGeometryResult = results.first(
where: { $0.target == .existingPlaneGeometry }
) {
return existingPlaneUsingGeometryResult
/// Uses interpolation between orientations to create a smooth `easeOut` orientation adjustment animation.
internal func performAlignmentAnimation(to newOrientation: simd_quatf) {
// Interpolate between current and target orientations.
orientation = simd_slerp(orientation, newOrientation, 0.15)
// This length creates a normalized vector (of length 1) with all 3 components being equal.
self.isChangingAlignment = self.shouldContinueAlignAnim(to: newOrientation)
}

// 2. As a fallback, check for a result on estimated planes.
return results.first(where: { $0.target == .estimatedPlane })
}
func shouldContinueAlignAnim(to newOrientation: simd_quatf) -> Bool {
let testVector = simd_float3(repeating: 1 / sqrtf(3))
let point1 = orientation.act(testVector)
let point2 = newOrientation.act(testVector)
let vectorsDot = simd_dot(point1, point2)
// Stop interpolating when the rotations are close enough to each other.
return vectorsDot < 0.999
}

/// Uses interpolation between orientations to create a smooth `easeOut` orientation adjustment animation.
internal func performAlignmentAnimation(to newOrientation: simd_quatf) {
// Interpolate between current and target orientations.
orientation = simd_slerp(orientation, newOrientation, 0.15)
// This length creates a normalized vector (of length 1) with all 3 components being equal.
self.isChangingAlignment = self.shouldContinueAlignAnim(to: newOrientation)
}

func shouldContinueAlignAnim(to newOrientation: simd_quatf) -> Bool {
let testVector = simd_float3(repeating: 1 / sqrtf(3))
let point1 = orientation.act(testVector)
let point2 = newOrientation.act(testVector)
let vectorsDot = simd_dot(point1, point2)
// Stop interpolating when the rotations are close enough to each other.
return vectorsDot < 0.999
}

/**
Reduce visual size change with distance by scaling up when close and down when far away.

These adjustments result in a scale of 1.0x for a distance of 0.7 m or less
(estimated distance when looking at a table), and a scale of 1.2x
for a distance 1.5 m distance (estimated distance when looking at the floor).
*/
internal func scaleBasedOnDistance(camera: ARCamera?) -> Float {
guard let camera = camera else { return 1.0 }

let distanceFromCamera = simd_length(self.convert(position: .zero, to: nil) - camera.transform.translation)
if distanceFromCamera < 0.7 {
return distanceFromCamera / 0.7
} else {
return 0.25 * distanceFromCamera + 0.825
/**
Reduce visual size change with distance by scaling up when close and down when far away.

These adjustments result in a scale of 1.0x for a distance of 0.7 m or less
(estimated distance when looking at a table), and a scale of 1.2x
for a distance 1.5 m distance (estimated distance when looking at the floor).
*/
internal func scaleBasedOnDistance(camera: ARCamera?) -> Float {
guard let camera = camera else { return 1.0 }

let distanceFromCamera = simd_length(self.convert(position: .zero, to: nil) - camera.transform.translation)
if distanceFromCamera < 0.7 {
return distanceFromCamera / 0.7
} else {
return 0.25 * distanceFromCamera + 0.825
}
}
}
}
#endif
Loading