diff --git a/Compatibility.md b/Compatibility.md index c3b8eec..dea0894 100644 --- a/Compatibility.md +++ b/Compatibility.md @@ -1,6 +1,6 @@ # Compatible Applications/Games -**ATTENTION** This list is a work in progress. Feel free to open a Pull Request updating it. +**ATTENTION** This list is incomplete and it is a work in progress. The app might work with some other Quest titles, feel free to open a Pull Request adding more titles to the list. You can find a list of the officially supported games/applications [here](https://creator.oculus.com/mrc/). @@ -23,5 +23,5 @@ You can find a list of the officially supported games/applications [here](https: | [Richie's Plank Experience](https://www.oculus.com/experiences/quest/1642239225880682) | ? | ? | [Tweet](https://twitter.com/FreekTeunen/status/1327673218891649024) | ? | | [SUPERHOT](https://www.oculus.com/experiences/quest/1921533091289407/) | 5 | 5 | [YouTube video](https://www.youtube.com/watch?v=ZnOY8juMw4k) | Works perfectly. | | [Space Pirate Trainer](https://www.oculus.com/experiences/quest/1663790613725314) | 5 | 5 | [YouTube video](https://www.youtube.com/watch?v=44Nmv7Es5yI) | Works perfectly. | -| [Tilt Brush](https://www.oculus.com/experiences/quest/2322529091093901) | 0 | 0 | ? | The video output is glitching and the camera doesn't appear to be respecting the calibration. | +| [Tilt Brush](https://www.oculus.com/experiences/quest/2322529091093901) | 0 | 0 | [Tweet](https://twitter.com/fabio914/status/1332724521497931777) | The video output is glitching and the camera doesn't appear to be respecting the calibration. | | [The Thrill of the Fight](https://www.oculus.com/experiences/quest/3008315795852749) | 4 | 5 | [YouTube video](https://www.youtube.com/watch?v=aPSBmej4ppc) | You'll have to use the "Use magenta for transparency" option. | diff --git a/Instructions.md b/Instructions.md index 9a4ce81..3ebc84b 100644 --- a/Instructions.md +++ b/Instructions.md @@ -4,7 +4,7 @@ - Oculus Quest 1 or 2 with the [Oculus MRC Calibration app](https://www.oculus.com/experiences/quest/2532132800176262/) version 1.7 installed. - iPhone or iPad with an A12 chip or newer, running iOS 14. The LiDAR sensor is optional but recommended for better results. - - 5 Ghz WiFi network. + - 5 Ghz WiFi network (802.11ac). - A [compatible Quest VR application/game](Compatibility.md). ## Calibration, what is this? diff --git a/README.md b/README.md index 85332c5..f0702dc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Follow us on [Twitter](https://twitter.com/reality_mixer) for more updates! - Oculus Quest 1 or 2 with the [Oculus MRC Calibration app](https://www.oculus.com/experiences/quest/2532132800176262/) version 1.7 installed. - iPhone or iPad with an A12 chip or newer, running iOS 14. The LiDAR sensor is optional but recommended for better results. - - 5 Ghz WiFi network. + - 5 Ghz WiFi network (802.11ac). - A [compatible Quest VR application/game](Compatibility.md). Note that this app is still just a prototype and it's still being developed, use at your own risk. diff --git a/RealityMixer.xcodeproj/project.pbxproj b/RealityMixer.xcodeproj/project.pbxproj index efc99c3..4c512da 100644 --- a/RealityMixer.xcodeproj/project.pbxproj +++ b/RealityMixer.xcodeproj/project.pbxproj @@ -932,7 +932,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = ZTJ55TM37B; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -948,7 +948,7 @@ "$(inherited)", "$(PROJECT_DIR)/RealityMixer/FFmpeg-iOS/lib", ); - MARKETING_VERSION = 0.1.4; + MARKETING_VERSION = 0.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.bluenose.reality.mixer; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "RealityMixer/RealityMixer-Bridging-Header.h"; @@ -965,7 +965,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = ZTJ55TM37B; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -981,7 +981,7 @@ "$(inherited)", "$(PROJECT_DIR)/RealityMixer/FFmpeg-iOS/lib", ); - MARKETING_VERSION = 0.1.4; + MARKETING_VERSION = 0.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.bluenose.reality.mixer; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "RealityMixer/RealityMixer-Bridging-Header.h"; diff --git a/RealityMixer/Calibration/Builders/CalibrationSceneNodeBuilder.swift b/RealityMixer/Calibration/Builders/CalibrationSceneNodeBuilder.swift index 859f3bf..d6c3bb6 100644 --- a/RealityMixer/Calibration/Builders/CalibrationSceneNodeBuilder.swift +++ b/RealityMixer/Calibration/Builders/CalibrationSceneNodeBuilder.swift @@ -36,19 +36,27 @@ struct CalibrationSceneNodeBuilder { let leftController: SCNNode! = model(named: "left") leftController.geometry?.firstMaterial?.lightingModel = .constant leftController.geometry?.firstMaterial?.diffuse.contents = UIColor.red - leftController.rotation = SCNVector4(1, 0, 0, 45.0 * .pi/180.0) + leftController.position = SCNVector3(-0.004, 0.027, 0.04) + + let rotatedLeftController = SCNNode() + rotatedLeftController.addChildNode(leftController) + rotatedLeftController.rotation = SCNVector4(1, 0, 0, 45.0 * .pi/180.0) let leftControllerNode = SCNNode() - leftControllerNode.addChildNode(leftController) + leftControllerNode.addChildNode(rotatedLeftController) mainNode.addChildNode(leftControllerNode) let rightController: SCNNode! = model(named: "right") rightController.geometry?.firstMaterial?.lightingModel = .constant rightController.geometry?.firstMaterial?.diffuse.contents = UIColor.blue - rightController.rotation = SCNVector4(1, 0, 0, 45.0 * .pi/180.0) + rightController.position = SCNVector3(0.004, 0.027, 0.04) + + let rotatedRightController = SCNNode() + rotatedRightController.addChildNode(rightController) + rotatedRightController.rotation = SCNVector4(1, 0, 0, 45.0 * .pi/180.0) let rightControllerNode = SCNNode() - rightControllerNode.addChildNode(rightController) + rightControllerNode.addChildNode(rotatedRightController) mainNode.addChildNode(rightControllerNode) // Using Quest 2 dimensions @@ -56,8 +64,12 @@ struct CalibrationSceneNodeBuilder { headset.firstMaterial?.lightingModel = .constant headset.firstMaterial?.diffuse.contents = UIColor.gray + let headsetIntermediateNode = SCNNode() + headsetIntermediateNode.geometry = headset + headsetIntermediateNode.position = SCNVector3(0, 0.025, 0) + let headsetNode = SCNNode() - headsetNode.geometry = headset + headsetNode.addChildNode(headsetIntermediateNode) mainNode.addChildNode(headsetNode) return .init( diff --git a/RealityMixer/Calibration/ViewControllers/CalibrationConnectionViewController.xib b/RealityMixer/Calibration/ViewControllers/CalibrationConnectionViewController.xib index 3c68bb2..7c6a81e 100644 --- a/RealityMixer/Calibration/ViewControllers/CalibrationConnectionViewController.xib +++ b/RealityMixer/Calibration/ViewControllers/CalibrationConnectionViewController.xib @@ -29,184 +29,182 @@ - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + - - - - - - + + + + + - @@ -214,11 +212,12 @@ + - + + - diff --git a/RealityMixer/Calibration/ViewControllers/CalibrationViewController.swift b/RealityMixer/Calibration/ViewControllers/CalibrationViewController.swift index 48d883d..2281466 100644 --- a/RealityMixer/Calibration/ViewControllers/CalibrationViewController.swift +++ b/RealityMixer/Calibration/ViewControllers/CalibrationViewController.swift @@ -158,12 +158,11 @@ final class CalibrationViewController: UIViewController { delegate: self ) - let navigationController = UINavigationController(rootViewController: viewController) + viewController.isModalInPresentation = true + viewController.modalPresentationStyle = .overFullScreen + viewController.modalTransitionStyle = .crossDissolve - navigationController.isModalInPresentation = true - navigationController.modalPresentationStyle = .fullScreen - - present(navigationController, animated: true, completion: nil) + present(viewController, animated: true, completion: nil) case .calibrationSet(let transform, _): saveButtonContainer.isHidden = false updateInfo("Step 4 of 4: Review your calibration and tap on \"Save to Headset\" to save it. ") @@ -221,7 +220,7 @@ final class CalibrationViewController: UIViewController { alert.addAction(.init(title: "OK", style: .default, handler: nil)) present(alert, animated: true, completion: nil) case .success: - let alert = UIAlertController(title: "Calibration Saved!", message: nil, preferredStyle: .alert) + let alert = UIAlertController(title: "Calibration Saved!", message: "You can now close the Oculus Mixed Reality Capture Calibration app and launch your VR application/game.", preferredStyle: .alert) alert.addAction(.init(title: "OK", style: .default, handler: { [weak self] _ in guard let self = self else { return } self.displayLink?.invalidate() diff --git a/RealityMixer/Calibration/ViewControllers/ProjectionViewController.swift b/RealityMixer/Calibration/ViewControllers/ProjectionViewController.swift index 493fe6b..8d58e4b 100644 --- a/RealityMixer/Calibration/ViewControllers/ProjectionViewController.swift +++ b/RealityMixer/Calibration/ViewControllers/ProjectionViewController.swift @@ -35,8 +35,13 @@ final class ProjectionViewController: UIViewController { @IBOutlet private weak var imageView: UIImageView! @IBOutlet private weak var sceneOverlay: SCNView! @IBOutlet private weak var blueView: UIView! + + @IBOutlet private weak var adjustDistanceButtonContainer: UIView! + @IBOutlet private weak var adjustDistanceContainer: UIView! @IBOutlet private weak var distanceLabel: UILabel! + @IBOutlet private weak var instructionsOverlayView: UIView! + private weak var mainNode: SCNNode? private let radius = CGFloat(20) @@ -57,6 +62,8 @@ final class ProjectionViewController: UIViewController { } } + private var first = true + init( scaleFactor: Double, cameraOrigin: Vector3, @@ -80,11 +87,15 @@ final class ProjectionViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - title = "Step 3 of 4" imageView.image = image - navigationItem.rightBarButtonItem = .init(title: "Done", style: .done, target: self, action: #selector(done)) - navigationItem.leftBarButtonItem = .init(title: "Cancel", style: .plain, target: self, action: #selector(cancel)) - buildScene() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + if first { + buildScene() + first = false + } } private func buildScene() { @@ -107,11 +118,19 @@ final class ProjectionViewController: UIViewController { let imageRatio = frame.camera.imageResolution.width/frame.camera.imageResolution.height if imageViewRatio > imageRatio { + let imageHeightInImageViewCoordinates = frame.camera.imageResolution.height * (imageView.frame.size.width/frame.camera.imageResolution.width) + let distanceInImageViewCoordinates = (imageHeightInImageViewCoordinates * 0.5)/CGFloat(tan(yFov/2.0)) + let adjustedYFov = CGFloat(2.0 * atan2((imageView.frame.size.height * 0.5), distanceInImageViewCoordinates)) + camera.projectionDirection = .vertical - camera.fieldOfView = CGFloat(yFov * (180.0/Float.pi)) + camera.fieldOfView = (adjustedYFov * (180.0/CGFloat.pi)) } else { + let imageWidthInImageViewCoordinates = frame.camera.imageResolution.width * (imageView.frame.size.height/frame.camera.imageResolution.height) + let distanceInImageViewCoordinates = (imageWidthInImageViewCoordinates * 0.5)/CGFloat(tan(xFov/2.0)) + let adjustedXFov = CGFloat(2.0 * atan2((imageView.frame.size.width * 0.5), distanceInImageViewCoordinates)) + camera.projectionDirection = .horizontal - camera.fieldOfView = CGFloat(xFov * (180.0/Float.pi)) + camera.fieldOfView = (adjustedXFov * (180.0/CGFloat.pi)) } let cameraNode = SCNNode() @@ -169,12 +188,32 @@ final class ProjectionViewController: UIViewController { distanceAdjustment = Double(sender.value) } - @objc private func done() { + @IBAction private func hideInstructionsAction(_ sender: UIButton) { + sender.isUserInteractionEnabled = false + + UIView.animateKeyframes( + withDuration: 0.2, + delay: 0, + animations: { [weak self] in + self?.instructionsOverlayView.alpha = 0 + }, + completion: { [weak self] _ in + self?.instructionsOverlayView.isHidden = true + } + ) + } + + @IBAction private func showDistanceAdjustmentAction(_ sender: UIButton) { + adjustDistanceButtonContainer.isHidden = true + adjustDistanceContainer.isHidden = false + } + + @IBAction private func done() { guard let currentResult = currentResult else { return } delegate?.projection(self, didFinishWithCalibration: currentResult.1, transform: currentResult.0) } - @objc private func cancel() { + @IBAction private func cancel() { delegate?.projectionDidCancel(self) } @@ -199,27 +238,32 @@ final class ProjectionViewController: UIViewController { blueView?.center = blueViewCenter } - // Using Aspect Fit + // Using Aspect Fill private func pixelCoordinate(from viewCoordinate: CGPoint) -> CGPoint { let imageViewRatio = imageView.frame.size.width/imageView.frame.size.height let imageRatio = image.size.width/image.size.height if imageViewRatio > imageRatio { + let imageHeightInImageViewCoordinates = image.size.height * (imageView.frame.size.width/image.size.width) + let offsetY = (imageHeightInImageViewCoordinates - imageView.frame.size.height)/2.0 + return CGPoint( - x: floor((viewCoordinate.x-((imageView.frame.size.width/2.0)-(((imageView.frame.size.height/image.size.height)*image.size.width)/2.0)))/(imageView.frame.size.height/image.size.height)), - y: floor(viewCoordinate.y/(imageView.frame.size.height/image.size.height)) + x: floor(viewCoordinate.x * (image.size.width/imageView.frame.size.width)), + y: floor((viewCoordinate.y + offsetY) * (image.size.height/imageHeightInImageViewCoordinates)) ) } else if imageViewRatio < imageRatio { - let halfAspectFit: CGFloat = ((imageView.frame.size.width/image.size.width)*image.size.height)/2.0 + let imageWidthInImageViewCoordinates = image.size.width * (imageView.frame.size.height/image.size.height) + let offsetX = (imageWidthInImageViewCoordinates - imageView.frame.size.width)/2.0 + return CGPoint( - x: floor(viewCoordinate.x/(imageView.frame.size.width/image.size.width)), - y: floor((viewCoordinate.y-((imageView.frame.size.width/2.0)-halfAspectFit))/(imageView.frame.size.height/image.size.height)) + x: floor((viewCoordinate.x + offsetX) * (image.size.width/imageWidthInImageViewCoordinates)), + y: floor(viewCoordinate.y * (image.size.height/imageView.frame.size.height)) ) } else { return CGPoint( - x: floor(viewCoordinate.x/(imageView.frame.size.width/image.size.width)), - y: floor(viewCoordinate.y/(imageView.frame.size.width/image.size.height)) + x: floor(viewCoordinate.x * (image.size.width/imageView.frame.size.width)), + y: floor(viewCoordinate.y * (image.size.height/imageView.frame.size.height)) ) } } diff --git a/RealityMixer/Calibration/ViewControllers/ProjectionViewController.xib b/RealityMixer/Calibration/ViewControllers/ProjectionViewController.xib index 76a8c74..61ef5fd 100644 --- a/RealityMixer/Calibration/ViewControllers/ProjectionViewController.xib +++ b/RealityMixer/Calibration/ViewControllers/ProjectionViewController.xib @@ -1,8 +1,8 @@ - - + + - + @@ -10,38 +10,34 @@ + + + - + - - + - - + + - + - + @@ -63,54 +59,224 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - + + - + + - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + diff --git a/RealityMixer/Capture/Shaders/Shaders.swift b/RealityMixer/Capture/Shaders/Shaders.swift index cd8f632..23f9c01 100644 --- a/RealityMixer/Capture/Shaders/Shaders.swift +++ b/RealityMixer/Capture/Shaders/Shaders.swift @@ -104,6 +104,7 @@ struct Shaders { vec3 magenta = vec3(1.0, 0.0, 1.0); float threshold = 0.10; + // FIXME: Consider using HSV to compare these colors bool checkRed = (alphaColor.r >= (magenta.r - threshold)); bool checkGreen = (alphaColor.g >= (magenta.g - threshold) && alphaColor.g <= (magenta.g + threshold)); bool checkBlue = (alphaColor.b >= (magenta.b - threshold)); diff --git a/RealityMixer/Capture/ViewControllers/MixedRealityConnectionViewController.xib b/RealityMixer/Capture/ViewControllers/MixedRealityConnectionViewController.xib index 3110416..16f2083 100644 --- a/RealityMixer/Capture/ViewControllers/MixedRealityConnectionViewController.xib +++ b/RealityMixer/Capture/ViewControllers/MixedRealityConnectionViewController.xib @@ -30,212 +30,210 @@ - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + - - - + + - - - + + - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + - - - - - - + + + + + - @@ -243,11 +241,12 @@ + - + - + diff --git a/RealityMixer/Main/ViewControllers/AboutViewController.xib b/RealityMixer/Main/ViewControllers/AboutViewController.xib index c130290..b8ec412 100644 --- a/RealityMixer/Main/ViewControllers/AboutViewController.xib +++ b/RealityMixer/Main/ViewControllers/AboutViewController.xib @@ -23,122 +23,120 @@ - - + + - - + + - - - - - - - - - + + - - + + + + + + + + + + + + - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - + - + - + + + + + + + - - - - - - + + + + + - @@ -146,13 +144,14 @@ - - + + + diff --git a/RealityMixer/Main/ViewControllers/InitialViewController.swift b/RealityMixer/Main/ViewControllers/InitialViewController.swift index 8dc1305..926480a 100644 --- a/RealityMixer/Main/ViewControllers/InitialViewController.swift +++ b/RealityMixer/Main/ViewControllers/InitialViewController.swift @@ -10,6 +10,8 @@ import SafariServices final class InitialViewController: UIViewController { + @IBOutlet private weak var scrollView: UIScrollView! + init() { super.init(nibName: String(describing: type(of: self)), bundle: Bundle(for: type(of: self))) } @@ -23,6 +25,11 @@ final class InitialViewController: UIViewController { title = "Reality Mixer" } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + scrollView.flashScrollIndicators() + } + @IBAction private func calibrateAction(_ sender: Any) { let otherNavigationController = UINavigationController(rootViewController: CalibrationConnectionViewController()) otherNavigationController.modalPresentationStyle = .overFullScreen diff --git a/RealityMixer/Main/ViewControllers/InitialViewController.xib b/RealityMixer/Main/ViewControllers/InitialViewController.xib index 5d09de9..25524a4 100644 --- a/RealityMixer/Main/ViewControllers/InitialViewController.xib +++ b/RealityMixer/Main/ViewControllers/InitialViewController.xib @@ -1,14 +1,15 @@ - + - + + @@ -20,96 +21,123 @@ - - + + - - - - + + + + + + + + + + - + + + - - - - - - - - - - - - - + - + + + + + - + + + - - + + + +