Skip to content

Commit

Permalink
Merge pull request #29 from maxxfrazer/style-update
Browse files Browse the repository at this point in the history
Swift Styling
  • Loading branch information
maxxfrazer authored Feb 27, 2022
2 parents 615af71 + 28bfdd5 commit d17dbbd
Show file tree
Hide file tree
Showing 9 changed files with 758 additions and 750 deletions.
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

0 comments on commit d17dbbd

Please sign in to comment.