Skip to content

Commit

Permalink
added visionOS gesture input support for ui controls
Browse files Browse the repository at this point in the history
  • Loading branch information
maxxfrazer committed Dec 17, 2023
1 parent fd963de commit 083256c
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 5 deletions.
3 changes: 3 additions & 0 deletions Sources/RealityUI/RUIButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public class RUIButton: Entity, HasButton, HasModel, HasPhysics {
self.button = button ?? ButtonComponent()
self.ruiOrientation()
self.makeModels()
#if os(visionOS)
self.components.set(InputTargetComponent())
#endif
self.components.set(RUIDragComponent(type: .click, delegate: self))
}

Expand Down
18 changes: 15 additions & 3 deletions Sources/RealityUI/RUIDragComponent+DragEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@

import RealityKit

// Extension for SIMD3<Double>
extension SIMD3 where Scalar == Double {
func toFloat3() -> SIMD3<Float> {
return SIMD3<Float>(Float(self.x), Float(self.y), Float(self.z))
}
}

extension RUIDragComponent {
/// Called when a drag interaction starts.
///
Expand All @@ -20,9 +27,12 @@ extension RUIDragComponent {
) -> Bool {
let worldPos = ray.origin + ray.direction
let localPos = entity.convert(position: worldPos, from: nil)
let dist = simd_length(ray.direction)
var dist = simd_length(ray.direction)
switch self.type {
case .move:
#if os(visionOS)
dist = 0 // we don't care about origin with visionos
#endif
self.touchState = .move(poi: localPos, distance: dist)
case .turn(let axis):
let plane = self.turnCollisionPlane(for: axis)
Expand Down Expand Up @@ -56,7 +66,9 @@ extension RUIDragComponent {
switch touchState {
case .move(let poi, let len):
handleMoveState(entity, newTouchPos, poi)
outputRay.direction = simd_normalize(ray.direction) * len
if len != 0 {
outputRay.direction = simd_normalize(ray.direction) * len
}
case .turn(let plane, let lastPoint): handleTurnState(entity, plane, lastPoint, &outputRay)
case .click(let selected):
if selected != hasCollided {
Expand All @@ -76,7 +88,7 @@ extension RUIDragComponent {
public func dragEnded(_ entity: Entity, ray: (origin: SIMD3<Float>, direction: SIMD3<Float>)) {
var outputRay = ray
switch self.touchState {
case .move(_, let len):
case .move(_, let len) where len != 0:
outputRay.direction = simd_normalize(ray.direction) * len
case .click(let selected):
if selected {
Expand Down
2 changes: 1 addition & 1 deletion Sources/RealityUI/RUIDragComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public class RUIDragComponent: Component {
/// - Returns: The collision point as `SIMD3<Float>` if a collision occurs, otherwise `nil`.
internal func getCollisionPoints(with ray: (origin: SIMD3<Float>, direction: SIMD3<Float>)) -> SIMD3<Float>? {
switch self.touchState {
case .move(_, let distance): ray.origin + normalize(ray.direction) * distance
case .move(_, let distance): ray.origin + (distance == 0 ? ray.direction : (normalize(ray.direction) * distance))
case .turn(let plane, _): self.findPointOnPlane(ray: ray, plane: plane)
case .click: ray.origin + ray.direction
case .none: nil
Expand Down
3 changes: 3 additions & 0 deletions Sources/RealityUI/RUISlider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ public extension HasSlider {
mesh: .generateSphere(radius: 0.5), materials: []
)
thumb.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.5)])
#if os(visionOS)
thumb.components.set(InputTargetComponent())
#endif
thumb.components.set(RUIDragComponent(
type: .move(.clamp(self.clampThumb)),
delegate: self as? RUIDragDelegate
Expand Down
4 changes: 4 additions & 0 deletions Sources/RealityUI/RUIStepper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ internal extension HasStepper {
rightModel.components.set(collider)
leftModel.components.set(RUIDragComponent(type: .click, delegate: self as? RUIDragDelegate))
rightModel.components.set(RUIDragComponent(type: .click, delegate: self as? RUIDragDelegate))
#if os(visionOS)
leftModel.components.set(InputTargetComponent())
rightModel.components.set(InputTargetComponent())
#endif

let background = self.addModel(part: .background)
background.model = ModelComponent(mesh: .generateBox(size: [2, 1, 0.25], cornerRadius: 0.125), materials: [])
Expand Down
3 changes: 3 additions & 0 deletions Sources/RealityUI/RUISwitch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ public extension HasSwitch {
let thumb = self.addModel(part: .thumb)
thumb.model = ModelComponent(mesh: .generateSphere(radius: (1 - padding) / 2), materials: [])
thumb.collision = CollisionComponent(shapes: [.generateSphere(radius: (1 - padding) / 2)])
#if os(visionOS)
thumb.components.set(InputTargetComponent())
#endif
thumb.components.set(RUIDragComponent(type: .move(.box(
BoundingBox(min: [-toggleXSpan, 0, 0], max: [toggleXSpan, 0, 0]))
), delegate: self as? RUIDragDelegate))
Expand Down
7 changes: 6 additions & 1 deletion Sources/RealityUI/RealityUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ import Combine
RealityUI.shared.logActivated()
}
/// Orientation of all RealityUI Entities upon creation. If nil, none will be set.
public static var startingOrientation: simd_quatf?
public static var startingOrientation: simd_quatf? {
#if os(visionOS)
return simd_quatf(angle: .pi, axis: [0, 1, 0])
#endif
return nil
}

/// Mask to exclude entities from being hit by the long/panning gesture
public static var longGestureMask: CollisionGroup = .all
Expand Down
77 changes: 77 additions & 0 deletions Sources/RealityUI/SwiftUI+RUIGestures.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// RUIDragDelegate.swift
//
//
// Created by Max Cobb on 20/11/2023.
//

import RealityKit
import SwiftUI

#if os(visionOS)
public extension View {
func addRUIDragGesture() -> some View {
self.gesture(RUIDragGesture())
}
func addRUITapGesture() -> some View {
self.gesture(RUITapGesture())
}
}

public struct RUITapGesture: Gesture {

public init() {}

public var body: some Gesture {
TapGesture().targetedToEntity(where: .has(RUITapComponent.self))
.onEnded { value in
value.entity.components.get(RUITapComponent.self)?.action(value.entity, nil)
}
}
}

internal extension EntityTargetValue where Value == DragGesture.Value {
var ray3D: Ray3D? {
guard let devicePose = self.inputDevicePose3D, let parent = self.entity.parent else { return nil }
let devicePos = self.convert(devicePose.position, from: .local, to: .scene)
let endPos = self.convert(self.location3D, from: .local, to: .scene)
let direction = endPos - devicePos
// print(endPos)
return Ray3D(origin: devicePos, direction: direction)
}
}

extension Ray3D {
var rayTuple: (SIMD3<Float>, SIMD3<Float>) {
(origin.vector.toFloat3(), direction.vector.toFloat3())
}
}

public struct RUIDragGesture: Gesture {
public var body: some Gesture {
DragGesture(minimumDistance: 0).targetedToEntity(where: .has(RUIDragComponent.self))
.onChanged { value in
guard let touchRay = value.ray3D,
let dragComp = value.entity.components[RUIDragComponent.self] else {
return
}
// print(value.location3D)
if value.entity.components[RUIComponent.self]?.ruiEnabled == false { return }

if dragComp.touchState == nil {
dragComp.dragStarted(value.entity, ray: touchRay.rayTuple)
} else {
dragComp.dragUpdated(value.entity, ray: touchRay.rayTuple, hasCollided: true)
}
}.onEnded { value in
guard let dragComp = value.entity.components[RUIDragComponent.self],
dragComp.touchState != nil, let touchRay = value.ray3D
else { return }
dragComp.dragEnded(value.entity, ray: touchRay.rayTuple)
}
}
public init() {
print("making new draggy")
}
}
#endif

0 comments on commit 083256c

Please sign in to comment.