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

make it usable for macOS and iOS #2

Merged
merged 2 commits into from Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 25 additions & 6 deletions Sources/CameraControlARView/ARViewContainer.swift
Expand Up @@ -5,27 +5,40 @@
// Created by Joseph Heck on 2/9/22.
//

#if os(iOS)
import UIKit
#elseif os(macOS)
import Cocoa
typealias UIViewRepresentable = NSViewRepresentable
#endif
import RealityKit
import SwiftUI

/// A SwiftUI representable view that wraps an underlying augmented reality view with camera controls instance.
///
/// Create an ``CameraControlARView`` externally and hand it into the container so that you can interact with the
/// view controls, or the underlying scene, from within SwiftUI.
public struct ARViewContainer: NSViewRepresentable {
public struct ARViewContainer: UIViewRepresentable {
sakrist marked this conversation as resolved.
Show resolved Hide resolved
/// The type of view this container wraps.
public typealias NSViewType = RealityKit.ARView

/// The wrapped ARView with camera controls enabled.
public var cameraARView: CameraControlARView
public var arView: CameraControlARView
sakrist marked this conversation as resolved.
Show resolved Hide resolved

// @Weak private var weakUpdates: []
sakrist marked this conversation as resolved.
Show resolved Hide resolved

/// Creates a coordinator to establish the view and to pass updates to and from the SwiftUI context hosting the view.
public func makeCoordinator() -> ARViewContainer.ARViewCoordinator {
ARViewCoordinator(self)
}

/// Creates a new SwiftUI view.
#if os(iOS)
public func makeUIView(context _: Context) -> ARView {
let arView = arView
return arView
}
#elseif os(macOS)
public func makeNSView(context _: Context) -> ARView {
// Creates the view object and configures its initial state.
//
Expand All @@ -34,19 +47,25 @@ public struct ARViewContainer: NSViewRepresentable {
// - transaction
// - environment

let arView = cameraARView
let arView = arView
return arView
}

#endif

#if os(iOS)
/// Updates the wrapped AR view with state information from SwiftUI.
public func updateNSView(_: ARView, context _: Context) {
public func updateUIView(_: ARView, context _: Context) {
// Updates the state of the specified view with new information from SwiftUI.
}
#elseif os(macOS)
public func updateNSView(_: ARView, context _: Context) {
}
#endif

/// Creates a new SwiftUI view that wraps and displays an augmented reality view.
/// - Parameter cameraARView: An instance of the camera-controlled AR View.
public init(cameraARView: CameraControlARView) {
self.cameraARView = cameraARView
self.arView = cameraARView
}

/// The coordinator object that facilitates to and from the wrapped view.
Expand Down
79 changes: 63 additions & 16 deletions Sources/CameraControlARView/CameraControlARView.swift
Expand Up @@ -5,8 +5,16 @@
// Created by Joseph Heck on 2/7/22.
//

import Cocoa
#if os(iOS)
import UIKit
import ARKit
#endif
#if os(macOS)
import AppKit
#endif
import RealityKit
import Foundation
import CoreGraphics

/// An augmented reality view for macOS that provides keyboard, trackpad, and mouse movement controls for the camera within the view.
///
Expand All @@ -17,7 +25,7 @@ import RealityKit
/// The default motion mode is ``MotionMode-swift.enum/arcball``.
///
/// Additional properties control the target location, the camera's location, or the speed of movement within the environment.
@objc public class CameraControlARView: ARView, ObservableObject {
public class CameraControlARView: ARView, ObservableObject {
/// The mode of camera motion within the augmented reality scene.
public enum MotionMode: Int {
/// Rotate around a target location, effectively orbiting and keeping the camera trained on it.
Expand Down Expand Up @@ -124,7 +132,7 @@ import RealityKit
/// This view doubles the speed valuewhen the key is held-down.
public var keyspeed: Float

private var dragstart: NSPoint
private var dragstart: CGPoint
private var dragstart_rotation: Float
private var dragstart_inclination: Float
private var magnify_start: Float
Expand Down Expand Up @@ -153,12 +161,21 @@ import RealityKit
/// A copy of the basic transform applied ot the camera, and updated in parallel to reflect "upward" to SwiftUI.
@Published var macOSCameraTransform: Transform

#if os(iOS)
var pinchGesture:UIPinchGestureRecognizer?
@IBAction func pinchRecognized(_ pinch: UIPinchGestureRecognizer) {
let multiplier = ((pinch.scale > 1.0) ? -1.0 : 1.0) * Float(pinch.scale)/100 // magnify_end
radius = radius * (multiplier + 1)
updateCamera()
}
#endif

/// Creates a new AR View with the camera controlled by mouse, keyboard, and/or the trackpad.
///
/// The default motion mode for the view is ``MotionMode-swift.enum/arcball``, which orbits the camera around a specific point in space.
///
/// - Parameter frameRect: The frame rectangle for the view, measured in points.
public required init(frame frameRect: NSRect) {
public required init(frame frameRect: CGRect) {
motionMode = .arcball

// ARCBALL mode
Expand All @@ -178,18 +195,24 @@ import RealityKit

// Not mode specific
cameraAnchor = AnchorEntity(world: .zero)
dragstart = NSPoint.zero
dragstart = CGPoint.zero
dragstart_transform = cameraAnchor.transform.matrix
// reflect the camera's transform as an observed object
macOSCameraTransform = cameraAnchor.transform
super.init(frame: frameRect)

#if os(macOS) || targetEnvironment(simulator)
let cameraEntity = PerspectiveCamera()
cameraEntity.camera.fieldOfViewInDegrees = 60
cameraAnchor.addChild(cameraEntity)
scene.addAnchor(cameraAnchor)

#endif
updateCamera()

#if os(iOS)
self.pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(pinchRecognized(_:)))
self.addGestureRecognizer(self.pinchGesture!)
#endif
}

// MARK: - rotational transforms
Expand Down Expand Up @@ -296,10 +319,7 @@ import RealityKit
fatalError("init(coder:) has not been implemented")
}

override open dynamic func mouseDown(with event: NSEvent) {
// print("mouseDown EVENT: \(event)")
// print(" at \(event.locationInWindow) of \(self.frame)")
dragstart = event.locationInWindow
func dragStart() {
switch motionMode {
case .arcball:
dragstart_rotation = rotationAngle
Expand All @@ -308,12 +328,8 @@ import RealityKit
dragstart_transform = cameraAnchor.transform.matrix
}
}

override open dynamic func mouseDragged(with event: NSEvent) {
// print("mouseDragged EVENT: \(event)")
// print(" at \(event.locationInWindow) of \(self.frame)")
let deltaX = Float(event.locationInWindow.x - dragstart.x)
let deltaY = Float(event.locationInWindow.y - dragstart.y)

func dragMove(_ deltaX: Float, _ deltaY: Float) {
switch motionMode {
case .arcball:
rotationAngle = dragstart_rotation - deltaX * dragspeed
Expand Down Expand Up @@ -342,6 +358,36 @@ import RealityKit
cameraAnchor.transform = Transform(matrix: combined_transform)
}
}

#if os(iOS)
override open dynamic func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
dragstart = touches.first!.location(in: self)
dragStart()
}

override open dynamic func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let drag = touches.first!.location(in: self)
let deltaX = Float(drag.x - dragstart.x)
let deltaY = Float(dragstart.y - drag.y)
dragMove(deltaX, deltaY)
}
#endif

#if os(macOS)
override open dynamic func mouseDown(with event: NSEvent) {
// print("mouseDown EVENT: \(event)")
// print(" at \(event.locationInWindow) of \(self.frame)")
dragstart = event.locationInWindow
dragStart()
}

override open dynamic func mouseDragged(with event: NSEvent) {
// print("mouseDragged EVENT: \(event)")
// print(" at \(event.locationInWindow) of \(self.frame)")
let deltaX = Float(event.locationInWindow.x - dragstart.x)
let deltaY = Float(event.locationInWindow.y - dragstart.y)
dragMove(deltaX, deltaY)
}

override open dynamic func keyDown(with event: NSEvent) {
// print("keyDown: \(event)")
Expand Down Expand Up @@ -482,4 +528,5 @@ import RealityKit
break
}
}
#endif
}
61 changes: 61 additions & 0 deletions Sources/CameraControlARView/RealityView.swift
@@ -0,0 +1,61 @@
//
// RealityView.swift
// CompleteAnatomy
//
// Created by Volodymyr Boichentsov on 11/10/2022.
// Copyright © 2022 3D4Medical, LLC. All rights reserved.
//

import SwiftUI
import Combine
import RealityKit

fileprivate let arContainer = ARViewContainer.init(cameraARView: CameraControlARView.init(frame: .zero))
var cancellables = [Cancellable]()

public struct RealityKitView: View {
public typealias UpdateBlock = () -> Void
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this (UpdateBlock) is public, give me some detail of what it's being used for, and why a developer would include an updateBlock closure in their RealityKitView initializer. (Ideally, this would have documentation to explain it - but I'm happy to write it up if you can detail what the intent is for me)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to remove alias :)


public struct Context {
public var base: RealityKit.Scene?


public func add(_ entity:Entity) {

let originAnchor = AnchorEntity(world: .zero)
originAnchor.addChild(entity)
arContainer.arView.scene.anchors.append(originAnchor)
}
}
let context = Context.init(base: arContainer.arView.scene)
var update: UpdateBlock?


public init(_ content: @escaping (_ context:Context) -> Void, update: UpdateBlock? = nil) {
content(context)
self.update = update

if let update = self.update {
let updateCa = arContainer.arView.scene.subscribe(to: SceneEvents.Update.self) { event in
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we name updateCa a more descriptive variable? I'm a little confused on this point - it makes sense to get the various SceneEvents as they're triggered, but you're not passing them down into the closure, so I'm not understanding the purpose of this all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will rename updateCa to updateCancellable probably forget to finish typing :)

Idea with update is that we can do something each frame. Update is optional argument.

update()
}
cancellables.append(updateCa)
}
}

public var body: some View {
arContainer
}
}

struct RealityView_Previews: PreviewProvider {
static var previews: some View {
RealityKitView( { context in
let entity = ModelEntity.init(mesh: .generateBox(size: SIMD3<Float>.init(repeating: 1)))
context.add(entity)
}, update: {
print("update")
}).frame(width: 300, height: 300)
}
}