├── LICENSE ├── README.md ├── Resources ├── Compass.png ├── Compass.pxm ├── Overlay.jpg ├── Overlay.pxm ├── POI.jpg ├── Search.png ├── Search.pxm ├── Search@2x.png └── Search@3x.png ├── Screenshots ├── TGLAugmentedRealityExample.jpg └── TGLAugmentedRealityExample2.jpg ├── TGLAugmentedRealityExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── mac.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── mac.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── TGLAugmentedRealityExample ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── Locations.imageset │ │ ├── Contents.json │ │ ├── Locations.png │ │ ├── Locations@2x.png │ │ └── Locations@3x.png │ └── Pin.imageset │ │ ├── Contents.json │ │ ├── Pin.png │ │ ├── Pin@2x.png │ │ └── Pin@3x.png ├── AugmentedViewController.h ├── AugmentedViewController.m ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Compass.png ├── Info.plist ├── PlaceOfInterest.h ├── PlaceOfInterest.m ├── PlaceOfInterestView.h ├── PlaceOfInterestView.m ├── SearchViewController.h ├── SearchViewController.m ├── Target.png ├── de.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── en.lproj │ ├── InfoPlist.strings │ ├── Localizable.strings │ └── Main.strings └── main.m ├── TGLAugmentedRealityView.podspec └── TGLAugmentedRealityView ├── TGLARBillboardImageShape.h ├── TGLARBillboardImageShape.m ├── TGLARCompass.h ├── TGLARCompassView.h ├── TGLARCompassView.m ├── TGLARImageShape.h ├── TGLARImageShape.m ├── TGLAROverlay.h ├── TGLAROverlayContainerView.h ├── TGLAROverlayContainerView.m ├── TGLARShapeOverlay.h ├── TGLARShapeOverlay.m ├── TGLARView.h ├── TGLARView.m ├── TGLARViewOverlay.h └── TGLARViewOverlay.m /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 PowerMobile Team (https://powermobile.org) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Platform](https://img.shields.io/cocoapods/p/TGLAugmentedRealityView.svg?maxAge=86400)](http://cocoadocs.org/docsets/TGLAugmentedRealityView) 2 | [![Tag](https://img.shields.io/github/tag/gleue/TGLAugmentedRealityView.svg?maxAge=86400)](https://github.com/powermobileteam/TGLAugmentedRealityView/tags) 3 | [![CocoaPods](https://img.shields.io/badge/CocoaPods-compatible-4BC51D.svg?style=flat)](https://cocoapods.org) 4 | [![License](https://img.shields.io/github/license/powermobileteam/TGLAugmentedRealityView.svg?maxAge=86400)](https://opensource.org/licenses/MIT) 5 | [![Downloads](https://img.shields.io/cocoapods/dt/TGLAugmentedRealityView.svg?maxAge=86400)](https://cocoapods.org/pods/TGLAugmentedRealityView) 6 | 7 | TGLAugmentedRealityView 8 | ======================= 9 | 10 | Place overlays on a camera preview and adjust their position depending on device attitude. 11 | 12 |

13 | TGLAugmentedRealityExample 14 |

15 | 16 | Getting Started 17 | =============== 18 | 19 | Take a look at sample project `TGLAugmentedRealityExample.xcodeproj`. 20 | 21 | Usage 22 | ===== 23 | 24 | Via [CocoaPods](http://cocoapods.org): 25 | 26 | * Add `pod 'TGLAugmentedRealityView', '~> 1.0'` to your project's `Podfile` 27 | 28 | Or the "classic" way: 29 | 30 | * Add files in folder `TGLAugmentedRealityView` to your project 31 | 32 | Then in your project: 33 | 34 | * Implement `TGLARViewDataSource` and `TGLARViewDelegate` protocols 35 | * Place a `TGLARView` in your storyboard and set its `-dataSource` and `-delegate` outlets 36 | 37 | Optionally: 38 | 39 | * Implement `TGLARCompass` protocol 40 | * Set `TGLARView`'s `-compass` outlet 41 | 42 | Sample 43 | ====== 44 | 45 | Build and run `TGLAugmentedRealityExample.xcodeproj` on an iDevice. The app will run on the 46 | simulator, too, but will give you no camera image or device orientation. 47 | 48 | Tap the blue button and search for POIs in the map view. When finished, close the map to 49 | reveal the AR view. The POIs will be displayed with a callout and a billboard image. 50 | 51 | On the AR view use a horizontal pan gesture to adjust the compass heading and a pinch 52 | gesture to adjust the zoom factor, if available in the active video format. 53 | 54 | Requirements 55 | ============ 56 | 57 | * ARC 58 | * iOS >= 9.3 59 | * Xcode 9 60 | 61 | License 62 | ======= 63 | 64 | TGLAugmentedRealityView is available under the MIT License (MIT) 65 | 66 | Copyright (c) 2015-2017 PowerMobile Team (http://powermobile.org) 67 | 68 | Permission is hereby granted, free of charge, to any person obtaining a copy 69 | of this software and associated documentation files (the "Software"), to deal 70 | in the Software without restriction, including without limitation the rights 71 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 72 | copies of the Software, and to permit persons to whom the Software is 73 | furnished to do so, subject to the following conditions: 74 | 75 | The above copyright notice and this permission notice shall be included in 76 | all copies or substantial portions of the Software. 77 | 78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 80 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 81 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 82 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 83 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 84 | THE SOFTWARE. 85 | -------------------------------------------------------------------------------- /Resources/Compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Compass.png -------------------------------------------------------------------------------- /Resources/Compass.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Compass.pxm -------------------------------------------------------------------------------- /Resources/Overlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Overlay.jpg -------------------------------------------------------------------------------- /Resources/Overlay.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Overlay.pxm -------------------------------------------------------------------------------- /Resources/POI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/POI.jpg -------------------------------------------------------------------------------- /Resources/Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Search.png -------------------------------------------------------------------------------- /Resources/Search.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Search.pxm -------------------------------------------------------------------------------- /Resources/Search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Search@2x.png -------------------------------------------------------------------------------- /Resources/Search@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Resources/Search@3x.png -------------------------------------------------------------------------------- /Screenshots/TGLAugmentedRealityExample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Screenshots/TGLAugmentedRealityExample.jpg -------------------------------------------------------------------------------- /Screenshots/TGLAugmentedRealityExample2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/Screenshots/TGLAugmentedRealityExample2.jpg -------------------------------------------------------------------------------- /TGLAugmentedRealityExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3D0E46571C071533003CBE4F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3D0E46551C071533003CBE4F /* Localizable.strings */; }; 11 | 3D0E465B1C0717EC003CBE4F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3D0E465D1C0717EC003CBE4F /* InfoPlist.strings */; }; 12 | 3D0E465F1C071950003CBE4F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3D0E46611C071950003CBE4F /* LaunchScreen.storyboard */; }; 13 | 3D701EE51BFF53410092DB4B /* PlaceOfInterestView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D701EE41BFF53410092DB4B /* PlaceOfInterestView.m */; }; 14 | 3D7AD0AF1BF0BDD300EB040C /* PlaceOfInterest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D7AD0AE1BF0BDD300EB040C /* PlaceOfInterest.m */; }; 15 | 3D7DF1761FEBBAA1009346C6 /* Compass.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D7DF1751FEBBAA0009346C6 /* Compass.png */; }; 16 | 3D7DF1781FEC04F9009346C6 /* Target.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D7DF1771FEC04F8009346C6 /* Target.png */; }; 17 | 3D8A19411C060FED00B91862 /* TGLARBillboardImageShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A19331C060FED00B91862 /* TGLARBillboardImageShape.m */; }; 18 | 3D8A19421C060FED00B91862 /* TGLARCompassView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A19351C060FED00B91862 /* TGLARCompassView.m */; }; 19 | 3D8A19431C060FED00B91862 /* TGLARImageShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A19371C060FED00B91862 /* TGLARImageShape.m */; }; 20 | 3D8A19441C060FED00B91862 /* TGLAROverlayContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A193A1C060FED00B91862 /* TGLAROverlayContainerView.m */; }; 21 | 3D8A19451C060FED00B91862 /* TGLARShapeOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A193C1C060FED00B91862 /* TGLARShapeOverlay.m */; }; 22 | 3D8A19461C060FED00B91862 /* TGLARView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A193E1C060FED00B91862 /* TGLARView.m */; }; 23 | 3D8A19471C060FED00B91862 /* TGLARViewOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8A19401C060FED00B91862 /* TGLARViewOverlay.m */; }; 24 | 3DAEF8671BF0954C0037E9C4 /* AugmentedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DAEF8611BF0954C0037E9C4 /* AugmentedViewController.m */; }; 25 | 3DCE74C81BECB2E800985E03 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE74C71BECB2E800985E03 /* main.m */; }; 26 | 3DCE74CB1BECB2E800985E03 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE74CA1BECB2E800985E03 /* AppDelegate.m */; }; 27 | 3DCE74CE1BECB2E800985E03 /* SearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE74CD1BECB2E800985E03 /* SearchViewController.m */; }; 28 | 3DCE74D11BECB2E800985E03 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3DCE74CF1BECB2E800985E03 /* Main.storyboard */; }; 29 | 3DCE74D31BECB2E800985E03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3DCE74D21BECB2E800985E03 /* Assets.xcassets */; }; 30 | 3DCE74DE1BECB30400985E03 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DCE74DD1BECB30400985E03 /* MapKit.framework */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 3D0E46501C06FF0F003CBE4F /* TGLARCompass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARCompass.h; sourceTree = ""; }; 35 | 3D0E46711C071C11003CBE4F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | 3D0E46721C071C11003CBE4F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 3D0E46731C071C51003CBE4F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; 38 | 3D0E46751C071C6C003CBE4F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 39 | 3D0E46761C071C76003CBE4F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 40 | 3D0E46791C071E01003CBE4F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 41 | 3D0E467A1C071E06003CBE4F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 42 | 3D701EE31BFF53410092DB4B /* PlaceOfInterestView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceOfInterestView.h; sourceTree = ""; }; 43 | 3D701EE41BFF53410092DB4B /* PlaceOfInterestView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceOfInterestView.m; sourceTree = ""; }; 44 | 3D7AD0AD1BF0BDD300EB040C /* PlaceOfInterest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceOfInterest.h; sourceTree = ""; }; 45 | 3D7AD0AE1BF0BDD300EB040C /* PlaceOfInterest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceOfInterest.m; sourceTree = ""; }; 46 | 3D7DF1751FEBBAA0009346C6 /* Compass.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Compass.png; sourceTree = ""; }; 47 | 3D7DF1771FEC04F8009346C6 /* Target.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Target.png; sourceTree = ""; }; 48 | 3D8A19321C060FED00B91862 /* TGLARBillboardImageShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARBillboardImageShape.h; sourceTree = ""; }; 49 | 3D8A19331C060FED00B91862 /* TGLARBillboardImageShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARBillboardImageShape.m; sourceTree = ""; }; 50 | 3D8A19341C060FED00B91862 /* TGLARCompassView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARCompassView.h; sourceTree = ""; }; 51 | 3D8A19351C060FED00B91862 /* TGLARCompassView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARCompassView.m; sourceTree = ""; }; 52 | 3D8A19361C060FED00B91862 /* TGLARImageShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARImageShape.h; sourceTree = ""; }; 53 | 3D8A19371C060FED00B91862 /* TGLARImageShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARImageShape.m; sourceTree = ""; }; 54 | 3D8A19381C060FED00B91862 /* TGLAROverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLAROverlay.h; sourceTree = ""; }; 55 | 3D8A19391C060FED00B91862 /* TGLAROverlayContainerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLAROverlayContainerView.h; sourceTree = ""; }; 56 | 3D8A193A1C060FED00B91862 /* TGLAROverlayContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLAROverlayContainerView.m; sourceTree = ""; }; 57 | 3D8A193B1C060FED00B91862 /* TGLARShapeOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARShapeOverlay.h; sourceTree = ""; }; 58 | 3D8A193C1C060FED00B91862 /* TGLARShapeOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARShapeOverlay.m; sourceTree = ""; }; 59 | 3D8A193D1C060FED00B91862 /* TGLARView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARView.h; sourceTree = ""; }; 60 | 3D8A193E1C060FED00B91862 /* TGLARView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARView.m; sourceTree = ""; }; 61 | 3D8A193F1C060FED00B91862 /* TGLARViewOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGLARViewOverlay.h; sourceTree = ""; }; 62 | 3D8A19401C060FED00B91862 /* TGLARViewOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGLARViewOverlay.m; sourceTree = ""; }; 63 | 3DAEF8601BF0954C0037E9C4 /* AugmentedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AugmentedViewController.h; sourceTree = ""; }; 64 | 3DAEF8611BF0954C0037E9C4 /* AugmentedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AugmentedViewController.m; sourceTree = ""; }; 65 | 3DCE74C31BECB2E800985E03 /* TGLARViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TGLARViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 3DCE74C71BECB2E800985E03 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 67 | 3DCE74C91BECB2E800985E03 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 68 | 3DCE74CA1BECB2E800985E03 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 69 | 3DCE74CC1BECB2E800985E03 /* SearchViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SearchViewController.h; sourceTree = ""; }; 70 | 3DCE74CD1BECB2E800985E03 /* SearchViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SearchViewController.m; sourceTree = ""; }; 71 | 3DCE74D21BECB2E800985E03 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 72 | 3DCE74D71BECB2E800985E03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 3DCE74DD1BECB30400985E03 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | 3DCE74C01BECB2E800985E03 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 3DCE74DE1BECB30400985E03 /* MapKit.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 3D8A19311C060FED00B91862 /* TGLAugmentedRealityView */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 3D8A19321C060FED00B91862 /* TGLARBillboardImageShape.h */, 92 | 3D8A19331C060FED00B91862 /* TGLARBillboardImageShape.m */, 93 | 3D0E46501C06FF0F003CBE4F /* TGLARCompass.h */, 94 | 3D8A19341C060FED00B91862 /* TGLARCompassView.h */, 95 | 3D8A19351C060FED00B91862 /* TGLARCompassView.m */, 96 | 3D8A19361C060FED00B91862 /* TGLARImageShape.h */, 97 | 3D8A19371C060FED00B91862 /* TGLARImageShape.m */, 98 | 3D8A19381C060FED00B91862 /* TGLAROverlay.h */, 99 | 3D8A19391C060FED00B91862 /* TGLAROverlayContainerView.h */, 100 | 3D8A193A1C060FED00B91862 /* TGLAROverlayContainerView.m */, 101 | 3D8A193B1C060FED00B91862 /* TGLARShapeOverlay.h */, 102 | 3D8A193C1C060FED00B91862 /* TGLARShapeOverlay.m */, 103 | 3D8A193D1C060FED00B91862 /* TGLARView.h */, 104 | 3D8A193E1C060FED00B91862 /* TGLARView.m */, 105 | 3D8A193F1C060FED00B91862 /* TGLARViewOverlay.h */, 106 | 3D8A19401C060FED00B91862 /* TGLARViewOverlay.m */, 107 | ); 108 | path = TGLAugmentedRealityView; 109 | sourceTree = ""; 110 | }; 111 | 3DAEF8551BF0952E0037E9C4 /* Resources */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 3D7DF1771FEC04F8009346C6 /* Target.png */, 115 | 3D7DF1751FEBBAA0009346C6 /* Compass.png */, 116 | 3DCE74CF1BECB2E800985E03 /* Main.storyboard */, 117 | 3DCE74D21BECB2E800985E03 /* Assets.xcassets */, 118 | 3D0E46611C071950003CBE4F /* LaunchScreen.storyboard */, 119 | 3D0E46551C071533003CBE4F /* Localizable.strings */, 120 | ); 121 | name = Resources; 122 | sourceTree = ""; 123 | }; 124 | 3DCE74BA1BECB2E800985E03 = { 125 | isa = PBXGroup; 126 | children = ( 127 | 3D8A19311C060FED00B91862 /* TGLAugmentedRealityView */, 128 | 3DCE74C51BECB2E800985E03 /* TGLAugmentedRealityExample */, 129 | 3DCE74C41BECB2E800985E03 /* Products */, 130 | ); 131 | sourceTree = ""; 132 | }; 133 | 3DCE74C41BECB2E800985E03 /* Products */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 3DCE74C31BECB2E800985E03 /* TGLARViewExample.app */, 137 | ); 138 | name = Products; 139 | sourceTree = ""; 140 | }; 141 | 3DCE74C51BECB2E800985E03 /* TGLAugmentedRealityExample */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 3DCE74C91BECB2E800985E03 /* AppDelegate.h */, 145 | 3DCE74CA1BECB2E800985E03 /* AppDelegate.m */, 146 | 3DAEF8601BF0954C0037E9C4 /* AugmentedViewController.h */, 147 | 3DAEF8611BF0954C0037E9C4 /* AugmentedViewController.m */, 148 | 3D7AD0AD1BF0BDD300EB040C /* PlaceOfInterest.h */, 149 | 3D7AD0AE1BF0BDD300EB040C /* PlaceOfInterest.m */, 150 | 3D701EE31BFF53410092DB4B /* PlaceOfInterestView.h */, 151 | 3D701EE41BFF53410092DB4B /* PlaceOfInterestView.m */, 152 | 3DCE74CC1BECB2E800985E03 /* SearchViewController.h */, 153 | 3DCE74CD1BECB2E800985E03 /* SearchViewController.m */, 154 | 3DCE74DF1BECB31200985E03 /* Frameworks */, 155 | 3DAEF8551BF0952E0037E9C4 /* Resources */, 156 | 3DCE74C61BECB2E800985E03 /* Supporting Files */, 157 | ); 158 | path = TGLAugmentedRealityExample; 159 | sourceTree = ""; 160 | }; 161 | 3DCE74C61BECB2E800985E03 /* Supporting Files */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 3DCE74D71BECB2E800985E03 /* Info.plist */, 165 | 3DCE74C71BECB2E800985E03 /* main.m */, 166 | 3D0E465D1C0717EC003CBE4F /* InfoPlist.strings */, 167 | ); 168 | name = "Supporting Files"; 169 | sourceTree = ""; 170 | }; 171 | 3DCE74DF1BECB31200985E03 /* Frameworks */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 3DCE74DD1BECB30400985E03 /* MapKit.framework */, 175 | ); 176 | name = Frameworks; 177 | path = ..; 178 | sourceTree = ""; 179 | }; 180 | /* End PBXGroup section */ 181 | 182 | /* Begin PBXNativeTarget section */ 183 | 3DCE74C21BECB2E800985E03 /* TGLAugmentedRealityExample */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = 3DCE74DA1BECB2E800985E03 /* Build configuration list for PBXNativeTarget "TGLAugmentedRealityExample" */; 186 | buildPhases = ( 187 | 3DCE74BF1BECB2E800985E03 /* Sources */, 188 | 3DCE74C01BECB2E800985E03 /* Frameworks */, 189 | 3DCE74C11BECB2E800985E03 /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = TGLAugmentedRealityExample; 196 | productName = TGLAugmentedRealityExample; 197 | productReference = 3DCE74C31BECB2E800985E03 /* TGLARViewExample.app */; 198 | productType = "com.apple.product-type.application"; 199 | }; 200 | /* End PBXNativeTarget section */ 201 | 202 | /* Begin PBXProject section */ 203 | 3DCE74BB1BECB2E800985E03 /* Project object */ = { 204 | isa = PBXProject; 205 | attributes = { 206 | LastUpgradeCheck = 0920; 207 | ORGANIZATIONNAME = "Tim Gleue"; 208 | TargetAttributes = { 209 | 3DCE74C21BECB2E800985E03 = { 210 | CreatedOnToolsVersion = 7.1; 211 | SystemCapabilities = { 212 | com.apple.Maps.iOS = { 213 | enabled = 1; 214 | }; 215 | }; 216 | }; 217 | }; 218 | }; 219 | buildConfigurationList = 3DCE74BE1BECB2E800985E03 /* Build configuration list for PBXProject "TGLAugmentedRealityExample" */; 220 | compatibilityVersion = "Xcode 3.2"; 221 | developmentRegion = English; 222 | hasScannedForEncodings = 0; 223 | knownRegions = ( 224 | de, 225 | Base, 226 | en, 227 | ); 228 | mainGroup = 3DCE74BA1BECB2E800985E03; 229 | productRefGroup = 3DCE74C41BECB2E800985E03 /* Products */; 230 | projectDirPath = ""; 231 | projectRoot = ""; 232 | targets = ( 233 | 3DCE74C21BECB2E800985E03 /* TGLAugmentedRealityExample */, 234 | ); 235 | }; 236 | /* End PBXProject section */ 237 | 238 | /* Begin PBXResourcesBuildPhase section */ 239 | 3DCE74C11BECB2E800985E03 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 3D7DF1781FEC04F9009346C6 /* Target.png in Resources */, 244 | 3D7DF1761FEBBAA1009346C6 /* Compass.png in Resources */, 245 | 3D0E465F1C071950003CBE4F /* LaunchScreen.storyboard in Resources */, 246 | 3D0E46571C071533003CBE4F /* Localizable.strings in Resources */, 247 | 3D0E465B1C0717EC003CBE4F /* InfoPlist.strings in Resources */, 248 | 3DCE74D31BECB2E800985E03 /* Assets.xcassets in Resources */, 249 | 3DCE74D11BECB2E800985E03 /* Main.storyboard in Resources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXResourcesBuildPhase section */ 254 | 255 | /* Begin PBXSourcesBuildPhase section */ 256 | 3DCE74BF1BECB2E800985E03 /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 3DCE74CE1BECB2E800985E03 /* SearchViewController.m in Sources */, 261 | 3DCE74CB1BECB2E800985E03 /* AppDelegate.m in Sources */, 262 | 3D8A19421C060FED00B91862 /* TGLARCompassView.m in Sources */, 263 | 3D8A19451C060FED00B91862 /* TGLARShapeOverlay.m in Sources */, 264 | 3DAEF8671BF0954C0037E9C4 /* AugmentedViewController.m in Sources */, 265 | 3D8A19431C060FED00B91862 /* TGLARImageShape.m in Sources */, 266 | 3D8A19471C060FED00B91862 /* TGLARViewOverlay.m in Sources */, 267 | 3D8A19461C060FED00B91862 /* TGLARView.m in Sources */, 268 | 3D8A19441C060FED00B91862 /* TGLAROverlayContainerView.m in Sources */, 269 | 3D7AD0AF1BF0BDD300EB040C /* PlaceOfInterest.m in Sources */, 270 | 3DCE74C81BECB2E800985E03 /* main.m in Sources */, 271 | 3D8A19411C060FED00B91862 /* TGLARBillboardImageShape.m in Sources */, 272 | 3D701EE51BFF53410092DB4B /* PlaceOfInterestView.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXVariantGroup section */ 279 | 3D0E46551C071533003CBE4F /* Localizable.strings */ = { 280 | isa = PBXVariantGroup; 281 | children = ( 282 | 3D0E46761C071C76003CBE4F /* en */, 283 | 3D0E46791C071E01003CBE4F /* de */, 284 | ); 285 | name = Localizable.strings; 286 | sourceTree = ""; 287 | }; 288 | 3D0E465D1C0717EC003CBE4F /* InfoPlist.strings */ = { 289 | isa = PBXVariantGroup; 290 | children = ( 291 | 3D0E46751C071C6C003CBE4F /* en */, 292 | 3D0E467A1C071E06003CBE4F /* de */, 293 | ); 294 | name = InfoPlist.strings; 295 | sourceTree = ""; 296 | }; 297 | 3D0E46611C071950003CBE4F /* LaunchScreen.storyboard */ = { 298 | isa = PBXVariantGroup; 299 | children = ( 300 | 3D0E46721C071C11003CBE4F /* Base */, 301 | ); 302 | name = LaunchScreen.storyboard; 303 | sourceTree = ""; 304 | }; 305 | 3DCE74CF1BECB2E800985E03 /* Main.storyboard */ = { 306 | isa = PBXVariantGroup; 307 | children = ( 308 | 3D0E46711C071C11003CBE4F /* Base */, 309 | 3D0E46731C071C51003CBE4F /* en */, 310 | ); 311 | name = Main.storyboard; 312 | sourceTree = ""; 313 | }; 314 | /* End PBXVariantGroup section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | 3DCE74D81BECB2E800985E03 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INFINITE_RECURSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 339 | CLANG_WARN_STRICT_PROTOTYPES = YES; 340 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | DEBUG_INFORMATION_FORMAT = dwarf; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | ENABLE_TESTABILITY = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_DYNAMIC_NO_PIC = NO; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_OPTIMIZATION_LEVEL = 0; 352 | GCC_PREPROCESSOR_DEFINITIONS = ( 353 | "DEBUG=1", 354 | "$(inherited)", 355 | ); 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 363 | MTL_ENABLE_DEBUG_INFO = YES; 364 | ONLY_ACTIVE_ARCH = YES; 365 | SDKROOT = iphoneos; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | 3DCE74D91BECB2E800985E03 /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 380 | CLANG_WARN_BOOL_CONVERSION = YES; 381 | CLANG_WARN_COMMA = YES; 382 | CLANG_WARN_CONSTANT_CONVERSION = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INFINITE_RECURSION = YES; 387 | CLANG_WARN_INT_CONVERSION = YES; 388 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 392 | CLANG_WARN_STRICT_PROTOTYPES = YES; 393 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 399 | ENABLE_NS_ASSERTIONS = NO; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu99; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 410 | MTL_ENABLE_DEBUG_INFO = NO; 411 | SDKROOT = iphoneos; 412 | TARGETED_DEVICE_FAMILY = "1,2"; 413 | VALIDATE_PRODUCT = YES; 414 | }; 415 | name = Release; 416 | }; 417 | 3DCE74DB1BECB2E800985E03 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | INFOPLIST_FILE = TGLAugmentedRealityExample/Info.plist; 422 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 423 | PRODUCT_BUNDLE_IDENTIFIER = "com.gleue-interactive.TGLAugmentedRealityExample"; 424 | PRODUCT_NAME = TGLARViewExample; 425 | }; 426 | name = Debug; 427 | }; 428 | 3DCE74DC1BECB2E800985E03 /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | INFOPLIST_FILE = TGLAugmentedRealityExample/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 434 | PRODUCT_BUNDLE_IDENTIFIER = "com.gleue-interactive.TGLAugmentedRealityExample"; 435 | PRODUCT_NAME = TGLARViewExample; 436 | }; 437 | name = Release; 438 | }; 439 | /* End XCBuildConfiguration section */ 440 | 441 | /* Begin XCConfigurationList section */ 442 | 3DCE74BE1BECB2E800985E03 /* Build configuration list for PBXProject "TGLAugmentedRealityExample" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | 3DCE74D81BECB2E800985E03 /* Debug */, 446 | 3DCE74D91BECB2E800985E03 /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | 3DCE74DA1BECB2E800985E03 /* Build configuration list for PBXNativeTarget "TGLAugmentedRealityExample" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 3DCE74DB1BECB2E800985E03 /* Debug */, 455 | 3DCE74DC1BECB2E800985E03 /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | /* End XCConfigurationList section */ 461 | }; 462 | rootObject = 3DCE74BB1BECB2E800985E03 /* Project object */; 463 | } 464 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TGLAugmentedRealityExample.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TGLAugmentedRealityExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 06.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface AppDelegate : UIResponder 29 | 30 | @property (strong, nonatomic) UIWindow *window; 31 | 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 06.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "AppDelegate.h" 27 | 28 | @interface AppDelegate () 29 | 30 | @end 31 | 32 | @implementation AppDelegate 33 | 34 | 35 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 36 | // Override point for customization after application launch. 37 | return YES; 38 | } 39 | 40 | - (void)applicationWillResignActive:(UIApplication *)application { 41 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 42 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 43 | } 44 | 45 | - (void)applicationDidEnterBackground:(UIApplication *)application { 46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | } 49 | 50 | - (void)applicationWillEnterForeground:(UIApplication *)application { 51 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 52 | } 53 | 54 | - (void)applicationDidBecomeActive:(UIApplication *)application { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | - (void)applicationWillTerminate:(UIApplication *)application { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Locations.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Locations@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Locations@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations@2x.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Locations.imageset/Locations@3x.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Pin.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Pin@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Pin@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin@2x.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Assets.xcassets/Pin.imageset/Pin@3x.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/AugmentedViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AugmentedViewController.h 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #import 29 | 30 | #import "PlaceOfInterest.h" 31 | 32 | @interface AugmentedViewController : UIViewController 33 | 34 | @property (nonatomic, assign) float userHeight; 35 | @property (nonatomic, strong) CLLocation *userLocation; 36 | 37 | @property (nonatomic, strong) NSArray *places; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/AugmentedViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AugmentedViewController.m 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "AugmentedViewController.h" 27 | #import "SearchViewController.h" 28 | 29 | #import "TGLARView.h" 30 | #import "TGLARImageShape.h" 31 | #import "TGLARBillboardImageShape.h" 32 | 33 | #import "PlaceOfInterestView.h" 34 | 35 | #import 36 | 37 | @interface AugmentedViewController () 38 | 39 | @property (weak, nonatomic) IBOutlet TGLARView *arView; 40 | @property (weak, nonatomic) IBOutlet UIButton *northButton; 41 | 42 | @property (nonatomic, strong) CLLocationManager *locationManager; 43 | 44 | @property (nonatomic, strong) PlaceOfInterest *userLocationPOI; 45 | 46 | @end 47 | 48 | @implementation AugmentedViewController 49 | 50 | #pragma mark - View lifecycle 51 | 52 | - (void)dealloc { 53 | 54 | self.places = nil; 55 | self.userLocationPOI = nil; 56 | } 57 | 58 | - (void)viewDidLoad { 59 | 60 | [super viewDidLoad]; 61 | 62 | self.locationManager = [[CLLocationManager alloc] init]; 63 | self.locationManager.delegate = self; 64 | 65 | self.userHeight = 1.6; 66 | self.userLocation = self.locationManager.location; 67 | 68 | self.northButton.enabled = self.arView.isMagenticNorthAvailable; 69 | 70 | // A single image shape in the X/Y plane at the user's location 71 | // 72 | PlaceOfInterest *userLocationPOI = [[PlaceOfInterest alloc] init]; 73 | 74 | userLocationPOI.title = NSLocalizedString(@"Standort", nil); 75 | userLocationPOI.targetPosition = GLKVector3Make(0, 0, -2.0 * self.userHeight); 76 | 77 | TGLARImageShape *shape = [[TGLARImageShape alloc] initWithContext:self.arView.renderContext size:CGSizeMake(2, 2) image:[UIImage imageNamed:@"Compass"]]; 78 | 79 | shape.transform = GLKMatrix4Multiply(GLKMatrix4MakeRotation(M_PI, 0, 0, 1), GLKMatrix4MakeRotation(-M_PI_2, 0, 1, 0)); 80 | 81 | userLocationPOI.overlayShape = shape; 82 | 83 | self.userLocationPOI = userLocationPOI; 84 | } 85 | 86 | - (void)viewWillAppear:(BOOL)animated { 87 | 88 | [super viewWillAppear:animated]; 89 | 90 | CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; 91 | 92 | [self updateLocationServiceAuthorizationStatus:status]; 93 | 94 | [self createOverlaysForPlaces:self.places]; 95 | [self.arView reloadData]; 96 | 97 | [self.arView start]; 98 | 99 | self.arView.interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation; 100 | } 101 | 102 | - (void)viewDidDisappear:(BOOL)animated { 103 | 104 | [super viewDidDisappear:animated]; 105 | 106 | [self.arView stop]; 107 | 108 | [self.locationManager stopUpdatingLocation]; 109 | } 110 | 111 | #pragma mark - Appearance 112 | 113 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations { 114 | 115 | return UIInterfaceOrientationMaskAll; 116 | } 117 | 118 | - (UIStatusBarStyle)preferredStatusBarStyle { 119 | 120 | return UIStatusBarStyleLightContent; 121 | } 122 | 123 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 124 | 125 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 126 | 127 | [coordinator animateAlongsideTransition:nil completion:^(id coordinator) { 128 | 129 | if ([UIApplication sharedApplication].statusBarOrientation != self.arView.interfaceOrientation) { 130 | 131 | self.arView.interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation; 132 | } 133 | }]; 134 | } 135 | 136 | - (IBAction)closeSearch:(UIStoryboardSegue *)segue { 137 | 138 | SearchViewController *controller = segue.sourceViewController; 139 | 140 | self.places = controller.foundPOIs; 141 | } 142 | 143 | #pragma mark - Accessors 144 | 145 | - (void)setUserHeight:(float)userHeight { 146 | 147 | _userHeight = userHeight; 148 | 149 | self.userLocationPOI.overlayShape.transform = GLKMatrix4MakeTranslation(0, 0, -self.userHeight); 150 | } 151 | 152 | - (void)setUserLocation:(CLLocation *)userLocation { 153 | 154 | if (userLocation) { 155 | 156 | _userLocation = userLocation; 157 | 158 | [self updatePlaceOverlayPositions]; 159 | } 160 | } 161 | 162 | - (void)setPlaces:(NSArray *)places { 163 | 164 | if (self.isViewLoaded) { 165 | 166 | [self destroyOverlaysForPlaces:self.places]; 167 | } 168 | 169 | _places = places; 170 | 171 | if (self.isViewLoaded) { 172 | 173 | [self createOverlaysForPlaces:self.places]; 174 | [self.arView reloadData]; 175 | } 176 | } 177 | 178 | #pragma mark - Navigation 179 | 180 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 181 | 182 | if ([segue.identifier isEqualToString:@"OpenSearch"]) { 183 | 184 | UINavigationController *navigation = segue.destinationViewController; 185 | SearchViewController *controller = (SearchViewController *)navigation.topViewController; 186 | 187 | controller.currentLocation = self.userLocation; 188 | controller.foundPOIs = self.places; 189 | } 190 | } 191 | 192 | #pragma mark - Actions 193 | 194 | - (IBAction)toggleTrueNorth:(id)sender { 195 | 196 | self.arView.useTrueNorth = !self.arView.isUsingTrueNorth; 197 | self.northButton.selected = self.arView.isUsingTrueNorth; 198 | } 199 | 200 | #pragma mark - CLLocationManagerDelegate protocol 201 | 202 | - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { 203 | 204 | [self updateLocationServiceAuthorizationStatus:status]; 205 | } 206 | 207 | - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { 208 | 209 | self.userLocation = locations.lastObject; 210 | } 211 | 212 | - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { 213 | 214 | if (error.code != kCLErrorLocationUnknown) { 215 | 216 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Standort kann nicht ermittelt werden", nil) 217 | message:error.localizedDescription 218 | preferredStyle:UIAlertControllerStyleAlert]; 219 | 220 | UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) 221 | style:UIAlertActionStyleCancel 222 | handler:nil]; 223 | 224 | [alert addAction:ok]; 225 | 226 | [self presentViewController:alert animated:YES completion:nil]; 227 | } 228 | } 229 | 230 | - (void)updateLocationServiceAuthorizationStatus:(CLAuthorizationStatus)status { 231 | 232 | if (status == kCLAuthorizationStatusRestricted || status == kCLAuthorizationStatusDenied) { 233 | 234 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Kein Zugriff auf Standort", nil) 235 | message:NSLocalizedString(@"Dein Standort kann nicht angezeigt werden.\n\nGehe zu den Einstellungen, um den Zugriff zu erlauben.", nil) 236 | preferredStyle:UIAlertControllerStyleAlert]; 237 | 238 | UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) 239 | style:UIAlertActionStyleCancel 240 | handler:nil]; 241 | 242 | [alert addAction:ok]; 243 | 244 | UIAlertAction *settings = [UIAlertAction actionWithTitle:NSLocalizedString(@"Einstellungen", nil) 245 | style:UIAlertActionStyleDefault 246 | handler:^ (UIAlertAction *action) { 247 | 248 | NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; 249 | 250 | [[UIApplication sharedApplication] openURL:url]; 251 | }]; 252 | 253 | [alert addAction:settings]; 254 | 255 | [self presentViewController:alert animated:YES completion:nil]; 256 | 257 | } else if (status == kCLAuthorizationStatusNotDetermined) { 258 | 259 | [self.locationManager requestWhenInUseAuthorization]; 260 | 261 | } else if (status == kCLAuthorizationStatusAuthorizedWhenInUse) { 262 | 263 | [self.locationManager startUpdatingLocation]; 264 | } 265 | } 266 | 267 | #pragma mark - TGLARViewDataSource protocol 268 | 269 | - (NSInteger)numberOfOverlaysInARView:(TGLARView *)arview { 270 | 271 | // All the POIs plus the user location overlay 272 | return self.places.count + 1; 273 | } 274 | 275 | - (id)arView:(TGLARView *)arview overlayAtIndex:(NSInteger)index { 276 | 277 | return (index < self.places.count) ? self.places[index] : self.userLocationPOI; 278 | } 279 | 280 | #pragma mark - TGLARViewDelegate protocol 281 | 282 | - (void)arView:(TGLARView *)arview didTapViewOverlay:(TGLARViewOverlay *)view { 283 | 284 | PlaceOfInterest *poi = (PlaceOfInterest *)view.overlay; 285 | 286 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Callout angetippt", nil) 287 | message:poi.title 288 | preferredStyle:UIAlertControllerStyleAlert]; 289 | 290 | UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) 291 | style:UIAlertActionStyleCancel 292 | handler:nil]; 293 | 294 | [alert addAction:ok]; 295 | 296 | [self presentViewController:alert animated:YES completion:nil]; 297 | } 298 | 299 | - (void)arView:(TGLARView *)arview didTapShapeOverlay:(TGLARShapeOverlay *)shape { 300 | 301 | PlaceOfInterest *poi = (PlaceOfInterest *)shape.overlay; 302 | 303 | if (poi != self.userLocationPOI) { 304 | 305 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Shape angetippt", nil) 306 | message:poi.title 307 | preferredStyle:UIAlertControllerStyleAlert]; 308 | 309 | UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) 310 | style:UIAlertActionStyleCancel 311 | handler:nil]; 312 | 313 | [alert addAction:ok]; 314 | 315 | [self presentViewController:alert animated:YES completion:nil]; 316 | } 317 | } 318 | 319 | - (CGFloat)arViewShapeOverlayNearClippingDistance:(TGLARView *)arview { 320 | 321 | return 1.0; 322 | } 323 | 324 | - (CGFloat)arViewShapeOverlayFarClippingDistance:(TGLARView *)arview { 325 | 326 | return 2000.0; 327 | } 328 | 329 | #pragma amrk - PlaceOfInterestViewDelegate protocol 330 | 331 | - (void)poiViewButtonTapped:(PlaceOfInterestView *)view { 332 | 333 | PlaceOfInterest *poi = (PlaceOfInterest *)view.overlay; 334 | 335 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Info angetippt", nil) 336 | message:poi.title 337 | preferredStyle:UIAlertControllerStyleAlert]; 338 | 339 | UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) 340 | style:UIAlertActionStyleCancel 341 | handler:nil]; 342 | 343 | [alert addAction:ok]; 344 | 345 | [self presentViewController:alert animated:YES completion:nil]; 346 | } 347 | 348 | #pragma mark - Helpers 349 | 350 | - (void)createOverlaysForPlaces:(NSArray *)places { 351 | 352 | for (PlaceOfInterest *place in places) { 353 | 354 | if (!place.overlayView) { 355 | 356 | PlaceOfInterestView *overlayView = [[PlaceOfInterestView alloc] init]; 357 | 358 | overlayView.place = place; 359 | overlayView.delegate = self; 360 | overlayView.calloutLineColor = [UIColor redColor]; 361 | overlayView.contentView.backgroundColor = [UIColor redColor]; 362 | 363 | place.overlayView = overlayView; 364 | } 365 | 366 | if (!place.overlayShape) { 367 | 368 | place.overlayShape = [[TGLARBillboardImageShape alloc] initWithContext:self.arView.renderContext size:CGSizeMake(100.0, 100.0) image:[UIImage imageNamed:@"Target"]]; 369 | } 370 | } 371 | 372 | [self updatePlaceOverlayPositions]; 373 | } 374 | 375 | - (void)updatePlaceOverlayPositions { 376 | 377 | // The overlay -targetPositions are relative to the 378 | // camera position, which is located at the origin. 379 | // 380 | // Therefore we compute the distances in the X and Y 381 | // directions and set the overlay position accordingly. 382 | // 383 | // NOTE: Since the POI altitued are always zero we 384 | // do not set the Z component here. 385 | // 386 | MKMapPoint userPoint = MKMapPointForCoordinate(self.userLocation.coordinate); 387 | 388 | for (PlaceOfInterest *place in self.places) { 389 | 390 | MKMapPoint overlayPoint = MKMapPointForCoordinate(place.coordinate); 391 | 392 | MKMapPoint westPoint = MKMapPointMake(overlayPoint.x, userPoint.y); 393 | CLLocationDistance westDistance = MKMetersBetweenMapPoints(userPoint, westPoint); 394 | 395 | if (userPoint.x < overlayPoint.x) westDistance = -westDistance; 396 | 397 | MKMapPoint northPoint = MKMapPointMake(userPoint.x, overlayPoint.y); 398 | CLLocationDistance northDistance = MKMetersBetweenMapPoints(userPoint, northPoint); 399 | 400 | if (userPoint.y < overlayPoint.y) northDistance = -northDistance; 401 | 402 | GLKVector3 overlayPosition = GLKVector3Make(northDistance, westDistance, (place == self.userLocationPOI) ? -2.0 * self.userHeight : 0.0); 403 | 404 | place.targetPosition = overlayPosition; 405 | } 406 | } 407 | 408 | - (void)destroyOverlaysForPlaces:(NSArray *)places { 409 | 410 | for (PlaceOfInterest *place in places) { 411 | 412 | place.overlayShape = nil; 413 | place.overlayView = nil; 414 | } 415 | } 416 | 417 | @end 418 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Compass.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | de_DE 7 | CFBundleDisplayName 8 | TGLAugmented 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0.4 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | CAMERA_USAGE 29 | NSLocationWhenInUseUsageDescription 30 | LOCATION_WHENINUSE_USAGE 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | gyroscope 39 | location-services 40 | opengles-2 41 | video-camera 42 | 43 | UIRequiresFullScreen 44 | 45 | UIStatusBarStyle 46 | UIStatusBarStyleLightContent 47 | UISupportedInterfaceOrientations 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | UIInterfaceOrientationPortraitUpsideDown 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/PlaceOfInterest.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceOfInterest.h 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 21.10.13. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | #import 29 | 30 | #import "TGLAROverlay.h" 31 | #import "TGLARViewOverlay.h" 32 | #import "TGLARShapeOverlay.h" 33 | 34 | @interface PlaceOfInterest : NSObject 35 | 36 | @property (nonatomic, copy) NSString *title; 37 | @property (nonatomic, readonly) CLPlacemark *placemark; 38 | 39 | @property (nonatomic, assign) GLKVector3 targetPosition; 40 | 41 | @property (nonatomic, strong) TGLARViewOverlay *overlayView; 42 | @property (nonatomic, strong) TGLARShapeOverlay *overlayShape; 43 | 44 | + (instancetype)placeOfInterestWithPlacemark:(CLPlacemark *)placemark; 45 | 46 | - (instancetype)initWithPlacemark:(CLPlacemark *)placemark; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/PlaceOfInterest.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceOfInterest.m 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 21.10.13. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "PlaceOfInterest.h" 27 | 28 | @implementation PlaceOfInterest 29 | 30 | + (instancetype)placeOfInterestWithPlacemark:(CLPlacemark *)placemark { 31 | 32 | return [[PlaceOfInterest alloc] initWithPlacemark:placemark]; 33 | } 34 | 35 | - (instancetype)initWithPlacemark:(CLPlacemark *)placemark { 36 | 37 | self = [super init]; 38 | 39 | if (self) { 40 | 41 | _placemark = placemark; 42 | } 43 | 44 | return self; 45 | } 46 | 47 | #pragma mark - MKAnnotation protocol 48 | 49 | - (CLLocationCoordinate2D)coordinate { 50 | 51 | return self.placemark.location.coordinate; 52 | } 53 | 54 | #pragma mark - TGLAROverlay protocol 55 | 56 | - (void)setOverlayView:(nullable TGLARViewOverlay *)overlayView { 57 | 58 | _overlayView = overlayView; 59 | 60 | self.overlayView.overlay = self; 61 | } 62 | 63 | - (void)setOverlayShape:(nullable TGLARShapeOverlay *)overlayShape { 64 | 65 | _overlayShape = overlayShape; 66 | 67 | self.overlayShape.overlay = self; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/PlaceOfInterestView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceOfInterestView.h 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 20.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARViewOverlay.h" 27 | 28 | #import "PlaceOfInterest.h" 29 | 30 | @class PlaceOfInterestView; 31 | 32 | @protocol PlaceOfInterestViewDelegate 33 | 34 | - (void)poiViewButtonTapped:(PlaceOfInterestView *)view; 35 | 36 | @end 37 | 38 | @interface PlaceOfInterestView : TGLARViewOverlay 39 | 40 | @property (nonatomic, weak) id delegate; 41 | 42 | @property (nonatomic, strong) PlaceOfInterest *place; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/PlaceOfInterestView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceOfInterestView.m 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 20.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "PlaceOfInterestView.h" 27 | 28 | @interface PlaceOfInterestView () 29 | 30 | @property (nonatomic, weak) UILabel *overlayLabel; 31 | @property (nonatomic, weak) UIButton *overlayButton; 32 | 33 | @end 34 | 35 | @implementation PlaceOfInterestView 36 | 37 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 38 | 39 | self = [super initWithCoder:aDecoder]; 40 | 41 | if (self) [self initView]; 42 | 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithFrame:(CGRect)frame { 47 | 48 | self = [super initWithFrame:frame]; 49 | 50 | if (self) [self initView]; 51 | 52 | return self; 53 | } 54 | 55 | - (void)initView { 56 | 57 | UILabel *overlayLabel = [[UILabel alloc] init]; 58 | 59 | overlayLabel.textColor = [UIColor whiteColor]; 60 | overlayLabel.numberOfLines = 0; 61 | overlayLabel.translatesAutoresizingMaskIntoConstraints = NO; 62 | 63 | [self.contentView addSubview:overlayLabel]; 64 | 65 | UIButton *overlayButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; 66 | 67 | overlayButton.tintColor = overlayLabel.textColor; 68 | overlayButton.translatesAutoresizingMaskIntoConstraints = NO; 69 | 70 | [overlayButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; 71 | 72 | [self.contentView addSubview:overlayButton]; 73 | 74 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[overlayButton]-[overlayLabel]-|" 75 | options:0 76 | metrics:nil 77 | views:NSDictionaryOfVariableBindings(overlayLabel, overlayButton)]]; 78 | 79 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[overlayLabel]-|" 80 | options:0 81 | metrics:nil 82 | views:NSDictionaryOfVariableBindings(overlayLabel, overlayButton)]]; 83 | 84 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:overlayButton 85 | attribute:NSLayoutAttributeCenterY 86 | relatedBy:NSLayoutRelationEqual 87 | toItem:self.contentView 88 | attribute:NSLayoutAttributeCenterY 89 | multiplier:1.0 90 | constant:0]]; 91 | self.overlayLabel = overlayLabel; 92 | self.overlayButton = overlayButton; 93 | } 94 | 95 | #pragma mark - Accessors 96 | 97 | - (void)setPlace:(PlaceOfInterest *)place { 98 | 99 | _place = place; 100 | 101 | self.overlayLabel.text = place.title; 102 | } 103 | 104 | #pragma mark - Actions 105 | 106 | - (IBAction)buttonTapped:(id)sender { 107 | 108 | [self.delegate poiViewButtonTapped:self]; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/SearchViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.h 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 06.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #import "PlaceOfInterest.h" 29 | 30 | @interface SearchViewController : UIViewController 31 | 32 | @property (nonatomic, strong) CLLocation *currentLocation; 33 | 34 | @property (nonatomic, strong) NSArray *foundPOIs; 35 | 36 | @end 37 | 38 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/SearchViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.m 3 | // TGLAugmentedRealityExample 4 | // 5 | // Created by Tim Gleue on 06.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "SearchViewController.h" 27 | 28 | #import 29 | 30 | static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve) { 31 | 32 | // See: http://stackoverflow.com/a/20188994 33 | // 34 | UIViewAnimationOptions opt = (UIViewAnimationOptions)curve; 35 | return opt << 16; 36 | } 37 | 38 | @interface SearchViewController () 39 | 40 | @property (weak, nonatomic) IBOutlet MKMapView *mapView; 41 | @property (weak, nonatomic) IBOutlet UISearchBar *searchBar; 42 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarBottomLayoutConstraint; 43 | 44 | @property (nonatomic, readonly) MKDistanceFormatter *distanceFormatter; 45 | 46 | @end 47 | 48 | @implementation SearchViewController 49 | 50 | @synthesize distanceFormatter = _distanceFormatter; 51 | 52 | - (void)dealloc { 53 | 54 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 55 | } 56 | 57 | - (void)viewDidLoad { 58 | 59 | [super viewDidLoad]; 60 | 61 | self.mapView.showsUserLocation = YES; 62 | self.mapView.userTrackingMode = MKUserTrackingModeNone; 63 | 64 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 65 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 66 | 67 | [self.searchBar becomeFirstResponder]; 68 | } 69 | 70 | - (void)viewWillAppear:(BOOL)animated { 71 | 72 | [super viewWillAppear:animated]; 73 | 74 | [self.mapView addAnnotations:self.foundPOIs]; 75 | } 76 | 77 | #pragma mark - Accessors 78 | 79 | - (void)setCurrentLocation:(CLLocation *)currentLocation { 80 | 81 | _currentLocation = currentLocation; 82 | 83 | MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(currentLocation.coordinate, 2000.0, 2000.0); 84 | 85 | [self.mapView setRegion:region animated:YES]; 86 | } 87 | 88 | - (MKDistanceFormatter *)distanceFormatter { 89 | 90 | if (_distanceFormatter == nil) { 91 | 92 | _distanceFormatter = [[MKDistanceFormatter alloc] init]; 93 | } 94 | 95 | return _distanceFormatter; 96 | } 97 | 98 | #pragma mark - MKMapViewDelegate protocol 99 | 100 | - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { 101 | 102 | if (![annotation isKindOfClass:MKUserLocation.class]) { 103 | 104 | static NSString * const ident = @"MapAnnotation"; 105 | 106 | MKAnnotationView *view = [mapView dequeueReusableAnnotationViewWithIdentifier:ident]; 107 | 108 | if (!view) { 109 | 110 | view = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ident]; 111 | 112 | view.image = [UIImage imageNamed:@"Pin"]; 113 | view.centerOffset = CGPointMake(0.0, -0.5 * view.image.size.height + 8.0); 114 | view.canShowCallout = YES; 115 | view.calloutOffset = CGPointMake(0.0, -0.1 * view.image.size.height); 116 | 117 | view.leftCalloutAccessoryView = [[UIImageView alloc] initWithImage:view.image]; 118 | 119 | UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; 120 | 121 | view.rightCalloutAccessoryView = button; 122 | 123 | UILabel *detailLabel = [[UILabel alloc] init]; 124 | 125 | detailLabel.font = [UIFont systemFontOfSize:10.0]; 126 | detailLabel.textColor = [UIColor lightGrayColor]; 127 | 128 | view.detailCalloutAccessoryView = detailLabel; 129 | 130 | } else { 131 | 132 | view.annotation = annotation; 133 | } 134 | 135 | if ([annotation isKindOfClass:PlaceOfInterest.class]) { 136 | 137 | UILabel *detailLabel = (UILabel *)view.detailCalloutAccessoryView; 138 | PlaceOfInterest *poi = (PlaceOfInterest *)annotation; 139 | 140 | CLLocationDistance dist = [poi.placemark.location distanceFromLocation:self.currentLocation]; 141 | 142 | detailLabel.text = [self.distanceFormatter stringFromDistance:dist]; 143 | } 144 | 145 | return view; 146 | } 147 | 148 | return nil; 149 | } 150 | 151 | - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { 152 | 153 | for (PlaceOfInterest *poi in self.foundPOIs) { 154 | 155 | if (poi == view.annotation) { 156 | 157 | [mapView removeAnnotation:poi]; 158 | 159 | NSMutableArray *pois = [NSMutableArray arrayWithArray:self.foundPOIs]; 160 | 161 | [pois removeObject:poi]; 162 | self.foundPOIs = pois; 163 | 164 | break; 165 | } 166 | } 167 | } 168 | 169 | #pragma mark - UISearchBarDelegate protocol 170 | 171 | - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { 172 | 173 | [searchBar resignFirstResponder]; 174 | 175 | MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; 176 | 177 | request.naturalLanguageQuery = searchBar.text; 178 | request.region = self.mapView.region; 179 | 180 | MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request]; 181 | 182 | [search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) { 183 | 184 | // Create a POI for each found item 185 | // 186 | NSMutableArray *places = [NSMutableArray array]; 187 | 188 | for (MKMapItem *item in response.mapItems) { 189 | 190 | PlaceOfInterest *poi = [PlaceOfInterest placeOfInterestWithPlacemark:item.placemark]; 191 | 192 | CLLocationDistance dist = [item.placemark.location distanceFromLocation:self.currentLocation]; 193 | 194 | poi.title = [NSString stringWithFormat:@"%@ (%@)", item.name, [self.distanceFormatter stringFromDistance:dist]]; 195 | 196 | [places addObject:poi]; 197 | } 198 | 199 | [self.mapView removeAnnotations:[self.mapView annotations]]; 200 | [self.mapView showAnnotations:places animated:YES]; 201 | 202 | self.foundPOIs = places; 203 | }]; 204 | } 205 | 206 | - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { 207 | 208 | [searchBar resignFirstResponder]; 209 | } 210 | 211 | #pragma mark - Notification handlers 212 | 213 | - (void)keyboardWillShow:(NSNotification *)notification { 214 | 215 | [self.searchBar setShowsCancelButton:YES animated:YES]; 216 | 217 | NSValue *value = notification.userInfo[UIKeyboardFrameEndUserInfoKey]; 218 | CGRect frame = [value CGRectValue]; 219 | 220 | self.searchBarBottomLayoutConstraint.constant = frame.size.height; 221 | 222 | if (@available(iOS 11, *)) self.searchBarBottomLayoutConstraint.constant -= self.view.safeAreaInsets.bottom; 223 | 224 | NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 225 | UIViewAnimationCurve curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; 226 | 227 | [UIView animateWithDuration:duration 228 | delay:0 229 | options:animationOptionsWithCurve(curve) 230 | animations:^ (void) { 231 | 232 | [self.view layoutIfNeeded]; 233 | 234 | } completion:nil]; 235 | } 236 | 237 | - (void)keyboardWillHide:(NSNotification *)notification { 238 | 239 | [self.searchBar setShowsCancelButton:NO animated:YES]; 240 | 241 | self.searchBarBottomLayoutConstraint.constant = 0; 242 | 243 | NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 244 | UIViewAnimationCurve curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; 245 | 246 | [UIView animateWithDuration:duration 247 | delay:0 248 | options:animationOptionsWithCurve(curve) 249 | animations:^ (void) { 250 | 251 | [self.view layoutIfNeeded]; 252 | 253 | } completion:nil]; 254 | } 255 | 256 | @end 257 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/Target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powermobileweb/TGLAugmentedRealityView/2360fc88458f2d07dd9d7412c0e5e25dcf45d9ce/TGLAugmentedRealityExample/Target.png -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/de.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | TGLAugmentedRealityExample 4 | 5 | Created by Tim Gleue on 26.11.15. 6 | Copyright © 2015 Tim Gleue. All rights reserved. 7 | */ 8 | 9 | "NSLocationWhenInUseUsageDescription" = "Dein Standort wird verwendet, um POIs in Deiner Nähe zu suchen und anzuzeigen."; 10 | "NSCameraUsageDescription" = "Die Kamera wird verwendet, um POIs in Deiner Nähe anzuzeigen."; 11 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TGLAugmentedRealityExample 4 | 5 | Created by Tim Gleue on 26.11.15. 6 | Copyright © 2015 Tim Gleue. All rights reserved. 7 | */ 8 | 9 | "OK" = "OK"; 10 | 11 | "Standort" = "Standort"; 12 | "Einstellungen" = "Einstellungen"; 13 | 14 | "Standort kann nicht ermittelt werden" = "Standort kann nicht ermittelt werden"; 15 | "Kein Zugriff auf Standort" = "Kein Zugriff auf Standort"; 16 | "Dein Standort kann nicht angezeigt werden.\n\nGehe zu den Einstellungen, um den Zugriff zu erlauben." = "Dein Standort kann nicht angezeigt werden.\n\nGehe zu den Einstellungen, um den Zugriff zu erlauben."; 17 | 18 | "N" = "N"; 19 | "NE" = "NE"; 20 | "E" = "E"; 21 | "SE" = "SE"; 22 | "S" = "S"; 23 | "SW" = "SW"; 24 | "W" = "W"; 25 | "NW" = "NW"; 26 | 27 | "Callout angetippt" = "Callout angetippt"; 28 | "Shape angetippt" = "Shape angetippt"; 29 | "Info angetippt" = "Info angetippt"; 30 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | TGLAugmentedRealityExample 4 | 5 | Created by Tim Gleue on 26.11.15. 6 | Copyright © 2015 Tim Gleue. All rights reserved. 7 | */ 8 | 9 | "NSLocationWhenInUseUsageDescription" = "Location will be used to find and display nearby POIs."; 10 | "NSCameraUsageDescription" = "Camera will be used to display nearby POIs."; 11 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TGLAugmentedRealityExample 4 | 5 | Created by Tim Gleue on 26.11.15. 6 | Copyright © 2015 Tim Gleue. All rights reserved. 7 | */ 8 | 9 | "OK" = "OK"; 10 | 11 | "Standort" = "Location"; 12 | "Einstellungen" = "Settings"; 13 | 14 | "Standort kann nicht ermittelt werden" = "Location Could Not Be Determined"; 15 | "Kein Zugriff auf Standort" = "Cannot Access Location"; 16 | "Dein Standort kann nicht angezeigt werden.\n\nGehe zu den Einstellungen, um den Zugriff zu erlauben." = "Your location cannot be displayed.\n\nGot to Settings to allow location access."; 17 | 18 | "N" = "N"; 19 | "NE" = "NE"; 20 | "E" = "E"; 21 | "SE" = "SE"; 22 | "S" = "S"; 23 | "SW" = "SW"; 24 | "W" = "W"; 25 | "NW" = "NW"; 26 | 27 | "Callout angetippt" = "Callout tapped"; 28 | "Shape angetippt" = "Shape tapped"; 29 | "Info angetippt" = "Info tapped"; 30 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/en.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UISearchBar"; placeholder = "POI"; ObjectID = "KbV-fU-cvd"; */ 3 | "KbV-fU-cvd.placeholder" = "POI"; 4 | 5 | /* Class = "UIButton"; disabledTitle = "Kein Kompass"; ObjectID = "Qf4-Qj-78z"; */ 6 | "Qf4-Qj-78z.disabledTitle" = "No Compass"; 7 | 8 | /* Class = "UIButton"; normalTitle = "Magnetisch N"; ObjectID = "Qf4-Qj-78z"; */ 9 | "Qf4-Qj-78z.normalTitle" = "Magnetic N"; 10 | 11 | /* Class = "UIButton"; selectedTitle = "Geographisch N"; ObjectID = "Qf4-Qj-78z"; */ 12 | "Qf4-Qj-78z.selectedTitle" = "Geographic N"; 13 | 14 | /* Class = "UINavigationItem"; title = "POI Suchen"; ObjectID = "dDA-fI-Tqz"; */ 15 | "dDA-fI-Tqz.title" = "POI Suchen"; 16 | -------------------------------------------------------------------------------- /TGLAugmentedRealityExample/main.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // main.m 4 | // TGLAugmentedRealityExample 5 | // 6 | // Created by Tim Gleue on 06.11.15. 7 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #import 28 | #import "AppDelegate.h" 29 | 30 | int main(int argc, char * argv[]) { 31 | @autoreleasepool { 32 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TGLAugmentedRealityView' 3 | s.version = '1.0.4' 4 | s.license = 'MIT' 5 | s.summary = 'Place overlays on a camera preview and adjust their position depending on device attitude.' 6 | s.homepage = 'https://github.com/gleue/TGLAugmentedRealityView' 7 | s.authors = { 'Tim Gleue' => 'info@powermobile.org' } 8 | s.source = { :git => 'https://github.com/[powermobileteam]/TGLAugmentedRealityView.git', :tag => s.version.to_s } 9 | s.source_files = 'TGLAugmentedRealityView' 10 | 11 | s.requires_arc = true 12 | s.platform = :ios, '8.0' 13 | end 14 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARBillboardImageShape.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARBillboardImageShape.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 19.11.15. 6 | // Copyright (c) 2015 PowerMobile Team ( http://powermobile.org ) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARImageShape.h" 27 | 28 | /// A @p TGLARImageShape automatically rotating towards the camera around its origin. 29 | @interface TGLARBillboardImageShape : TGLARImageShape 30 | 31 | /** Disables billboard transformation. Default is NO. */ 32 | @property (nonatomic, assign, getter=isLocked) BOOL locked; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARBillboardImageShape.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARBillboardImageShape.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 19.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARBillboardImageShape.h" 27 | 28 | @implementation TGLARBillboardImageShape 29 | 30 | - (BOOL)draw { 31 | 32 | if (self.locked) return [super draw]; 33 | 34 | // Compute billboard transform and 35 | // replace shape transform temporarily 36 | // for drawing 37 | // 38 | // see http://nehe.gamedev.net/article/billboarding_how_to/18011/ 39 | // 40 | GLKVector3 pos = self.overlay.targetPosition; 41 | GLKVector3 look = GLKVector3Normalize(GLKVector3Negate(pos)); 42 | GLKVector3 right = GLKVector3CrossProduct(GLKVector3Make(0, 0, 1), look); 43 | GLKVector3 up = GLKVector3CrossProduct(look, right); 44 | 45 | GLKMatrix4 billboard = GLKMatrix4Make(look.x, look.y, look.z, 0.0, right.x, right.y, right.z, 0.0, up.x, up.y, up.z, 0.0, pos.x, pos.y, pos.z, 1.0); 46 | GLKMatrix4 transform = self.transform; 47 | 48 | self.transform = GLKMatrix4Multiply(billboard, transform); 49 | 50 | BOOL ok = [super draw]; 51 | 52 | self.transform = transform; 53 | 54 | return ok; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARCompass.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARCompass.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 26.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | /// An object presenting the current heading angle from a @p TGLARView must adopt this protocol. 29 | @protocol TGLARCompass 30 | 31 | /// The current heading angle in degrees. 32 | - (void)setHeadingAngle:(CGFloat)headingAngle; 33 | 34 | @optional 35 | 36 | /** The current field of view in degrees. 37 | * 38 | * If the receiver responds to this selector, 39 | * the camera's horizontal field of view is set 40 | * here. 41 | */ 42 | - (void)setFieldOfView:(CGFloat)fieldOfView; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARCompassView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARCompassView.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 19.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | 29 | #import "TGLARCompass.h" 30 | 31 | IB_DESIGNABLE 32 | 33 | /** A @p UIView subclass presenting the current heading angle as a horizontal HUD. 34 | * 35 | * The HUD has an upper scale with 16 lines at 22.5 degrees, a lower scale with 36 | * 72 lines at 5 degrees and centered labels for the 8 main directions of the 37 | * compass rose. 38 | */ 39 | @interface TGLARCompassView : UIView 40 | 41 | /// The compass label font. Default is bold system font at 24 points. 42 | @property (nonatomic, copy, nullable) IBInspectable UIFont *labelFont; 43 | /// The compass label color. Default is @p [UIColor whiteColor]. 44 | @property (nonatomic, copy, nullable) IBInspectable UIColor *labelColor; 45 | 46 | /// The north direction color. Default is @p [UIColor redColor]. 47 | @property (nonatomic, copy, nullable) IBInspectable UIColor *northColor; 48 | /// The north scale line width. Default is @p 4.0. 49 | @property (nonatomic, assign) IBInspectable CGFloat northLineWidth; 50 | 51 | /// The upper compass scale color. Default is @p [UIColor whiteColor]. 52 | @property (nonatomic, copy, nullable) IBInspectable UIColor *topScaleColor; 53 | /// The upper scale line width. Default is @p 2.0. 54 | @property (nonatomic, assign) IBInspectable CGFloat topScaleLineWidth; 55 | 56 | /// The lower compass scale color. Default is @p [UIColor whiteColor]. 57 | @property (nonatomic, copy, nullable) IBInspectable UIColor *bottomScaleColor; 58 | /// The lower scale line width. Default is @p 2.0. 59 | @property (nonatomic, assign) IBInspectable CGFloat bottomScaleLineWidth; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARCompassView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARCompassView.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 19.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #import "TGLARCompassView.h" 29 | 30 | @interface TGLARCompassView () 31 | 32 | @property (nonatomic, assign) CGFloat fieldOfView; 33 | @property (nonatomic, assign) CGFloat headingAngle; 34 | 35 | @property (nonatomic, readonly) NSDictionary *compassLabels; 36 | 37 | @end 38 | 39 | @implementation TGLARCompassView 40 | 41 | @synthesize compassLabels = _compassLabels; 42 | 43 | #pragma mark - Accessors 44 | 45 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 46 | 47 | self = [super initWithCoder:aDecoder]; 48 | 49 | if (self) [self initCompass]; 50 | 51 | return self; 52 | } 53 | 54 | - (instancetype)initWithFrame:(CGRect)frame { 55 | 56 | self = [super initWithFrame:frame]; 57 | 58 | if (self) [self initCompass]; 59 | 60 | return self; 61 | } 62 | 63 | - (void)initCompass { 64 | 65 | #if TARGET_IPHONE_SIMULATOR 66 | _fieldOfView = 50.0; 67 | #endif 68 | 69 | _labelFont = [UIFont boldSystemFontOfSize:24.0]; 70 | _labelColor = [UIColor whiteColor]; 71 | 72 | _northColor = [UIColor redColor]; 73 | _northLineWidth = 4.0; 74 | 75 | _topScaleColor = [UIColor whiteColor]; 76 | _topScaleLineWidth = 2.0; 77 | 78 | _bottomScaleColor = [UIColor whiteColor]; 79 | _bottomScaleLineWidth = 2.0; 80 | } 81 | 82 | #pragma mark - Accessors 83 | 84 | - (void)setHeadingAngle:(CGFloat)headingAngle { 85 | 86 | _headingAngle = headingAngle; 87 | 88 | [self setNeedsDisplay]; 89 | } 90 | 91 | - (void)setFieldOfView:(CGFloat)fieldOfView { 92 | 93 | _fieldOfView = fieldOfView; 94 | 95 | [self setNeedsDisplay]; 96 | } 97 | 98 | - (void)setLabelFont:(UIFont *)labelFont { 99 | 100 | _labelFont = [labelFont copy]; 101 | 102 | [self setNeedsDisplay]; 103 | } 104 | 105 | - (void)setLabelColor:(UIColor *)labelColor { 106 | 107 | _labelColor = [labelColor copy]; 108 | 109 | [self setNeedsDisplay]; 110 | } 111 | 112 | - (void)setNorthColor:(UIColor *)northColor { 113 | 114 | _northColor = [northColor copy]; 115 | 116 | [self setNeedsDisplay]; 117 | } 118 | 119 | - (void)setNorthLineWidth:(CGFloat)northLineWidth { 120 | 121 | _northLineWidth = northLineWidth; 122 | 123 | [self setNeedsDisplay]; 124 | } 125 | 126 | - (void)setTopScaleColor:(UIColor *)topScaleColor { 127 | 128 | _topScaleColor = [topScaleColor copy]; 129 | 130 | [self setNeedsDisplay]; 131 | } 132 | 133 | - (void)setTopScaleLineWidth:(CGFloat)topScaleLineWidth { 134 | 135 | _topScaleLineWidth = topScaleLineWidth; 136 | 137 | [self setNeedsDisplay]; 138 | } 139 | 140 | - (void)setBottomScaleColor:(UIColor *)bottomScaleColor { 141 | 142 | _bottomScaleColor = [bottomScaleColor copy]; 143 | 144 | [self setNeedsDisplay]; 145 | } 146 | 147 | - (void)setBottomScaleLineWidth:(CGFloat)bottomScaleLineWidth { 148 | 149 | _bottomScaleLineWidth = bottomScaleLineWidth; 150 | 151 | [self setNeedsDisplay]; 152 | } 153 | 154 | - (NSDictionary *)compassLabels { 155 | 156 | if (_compassLabels == nil) { 157 | 158 | _compassLabels = @{ @(000.0): NSLocalizedString(@"N", nil), @(045.0): NSLocalizedString(@"NE", nil), 159 | @(090.0): NSLocalizedString(@"E", nil), @(135.0): NSLocalizedString(@"SE", nil), 160 | @(180.0): NSLocalizedString(@"S", nil), @(225.0): NSLocalizedString(@"SW", nil), 161 | @(270.0): NSLocalizedString(@"W", nil), @(315.0): NSLocalizedString(@"NW", nil) }; 162 | } 163 | 164 | return _compassLabels; 165 | } 166 | 167 | #pragma mark - Drawing 168 | 169 | #define TOPSCALE 16 170 | #define LABELSCALE 8 171 | #define BOTTOMSCALE 72 172 | 173 | - (void)drawRect:(CGRect)rect { 174 | 175 | [self.backgroundColor setFill]; 176 | UIRectFill(self.bounds); 177 | 178 | UIBezierPath *line = [UIBezierPath bezierPath]; 179 | 180 | CGFloat width = CGRectGetWidth(self.bounds); 181 | CGFloat width_2 = 0.5 * width; 182 | CGFloat height = CGRectGetHeight(self.bounds); 183 | CGFloat height_2 = 0.5 * height; 184 | 185 | [line moveToPoint:CGPointMake(width_2, 0.0)]; 186 | [line addLineToPoint:CGPointMake(width_2, height)]; 187 | 188 | CGFloat fov_2 = 0.5 * self.fieldOfView; 189 | CGFloat factor = CGRectGetWidth(self.bounds) / tanf(GLKMathDegreesToRadians(fov_2)); 190 | CGFloat startAngle = self.headingAngle - fov_2; 191 | CGFloat endAngle = self.headingAngle + fov_2 + 2.0; 192 | 193 | if (startAngle < 0.0) { 194 | 195 | startAngle += 360.0; 196 | endAngle += 360.0; 197 | } 198 | 199 | CGContextRef context = UIGraphicsGetCurrentContext(); 200 | 201 | // |(-fov/2) | (+fov/2)| 202 | // |<----------------+---------------->| 203 | // |-w/2 0 +w/2| 204 | // 205 | 206 | // Top scale 207 | { 208 | [self.topScaleColor setStroke]; 209 | 210 | CGFloat incr = 360.0 / TOPSCALE; 211 | NSInteger index = floorf(startAngle / incr); 212 | 213 | for (CGFloat angle = incr * index; angle <= endAngle; angle += incr, index++) { 214 | 215 | CGContextSaveGState(context); 216 | 217 | if (index % TOPSCALE) { 218 | 219 | line.lineWidth = self.topScaleLineWidth; 220 | 221 | } else { 222 | 223 | [self.northColor setStroke]; 224 | line.lineWidth = self.northLineWidth; 225 | } 226 | 227 | CGFloat delta = GLKMathDegreesToRadians(angle - self.headingAngle); 228 | CGFloat xoffset = factor * tanf(0.5 * delta); 229 | CGFloat yscale = 0.25; 230 | 231 | CGContextScaleCTM(context, 1.0, yscale); 232 | CGContextTranslateCTM(context, xoffset, 0.0); 233 | 234 | [line stroke]; 235 | 236 | CGContextRestoreGState(context); 237 | } 238 | } 239 | 240 | // Label scale 241 | { 242 | CGFloat incr = 360.0 / LABELSCALE; 243 | NSInteger index = floorf(startAngle / incr); 244 | 245 | for (CGFloat angle = incr * index; angle <= endAngle; angle += incr, index++) { 246 | 247 | NSString *label = self.compassLabels[@(incr * (index % LABELSCALE))]; 248 | 249 | if (label) { 250 | 251 | CGContextSaveGState(context); 252 | 253 | CGFloat delta = GLKMathDegreesToRadians(angle - self.headingAngle); 254 | CGFloat xoffset = factor * tanf(0.5 * delta); 255 | 256 | CGContextTranslateCTM(context, xoffset, 0.0); 257 | 258 | UIColor *labelColor = (index % LABELSCALE) ? self.labelColor : self.northColor; 259 | NSDictionary *labelAttributes = @{ NSFontAttributeName: self.labelFont, NSForegroundColorAttributeName: labelColor }; 260 | CGSize labelSize = [label sizeWithAttributes:labelAttributes]; 261 | 262 | [label drawAtPoint:CGPointMake(round(width_2 - 0.5 * labelSize.width), round(height_2 - 0.5 * labelSize.height)) withAttributes:labelAttributes]; 263 | 264 | CGContextRestoreGState(context); 265 | } 266 | } 267 | } 268 | 269 | // Bottom scale 270 | { 271 | [self.bottomScaleColor setStroke]; 272 | 273 | CGFloat incr = 360.0 / BOTTOMSCALE; 274 | NSInteger index = floorf(startAngle / incr); 275 | 276 | for (CGFloat angle = incr * index; angle <= endAngle; angle += incr, index++) { 277 | 278 | CGContextSaveGState(context); 279 | 280 | if (index % BOTTOMSCALE) { 281 | 282 | line.lineWidth = self.bottomScaleLineWidth; 283 | 284 | } else { 285 | 286 | [self.northColor setStroke]; 287 | line.lineWidth = self.northLineWidth; 288 | } 289 | 290 | CGFloat delta = GLKMathDegreesToRadians(angle - self.headingAngle); 291 | CGFloat xoffset = factor * tanf(0.5 * delta); 292 | CGFloat yscale = 0.25; 293 | 294 | CGContextScaleCTM(context, 1.0, yscale); 295 | CGContextTranslateCTM(context, xoffset, 0.75 * height / yscale); 296 | 297 | [line stroke]; 298 | 299 | CGContextRestoreGState(context); 300 | } 301 | } 302 | } 303 | 304 | #pragma mark - Interface Builder 305 | 306 | - (void)prepareForInterfaceBuilder { 307 | 308 | [super prepareForInterfaceBuilder]; 309 | 310 | self.headingAngle = 0; 311 | self.fieldOfView = 50; 312 | } 313 | 314 | @end 315 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARImageShape.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARImageShape.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 12.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARShapeOverlay.h" 27 | 28 | /// A rectangular 3D shape in Y/Z plane showing an image texture and facing the positive X axis. 29 | @interface TGLARImageShape : TGLARShapeOverlay 30 | 31 | /** Designated intializer. 32 | * 33 | * @param context OpenGL ES context to create shape in. 34 | * @param size Shape width and height in meters. 35 | * @param image The Image to apply as shape's texture. 36 | * 37 | * @return An initilized instance or nil if initialization fails. 38 | */ 39 | - (nullable instancetype)initWithContext:(nonnull EAGLContext *)context size:(CGSize)size image:(nullable UIImage *)image; 40 | 41 | /** Set the shape's texture image. 42 | * 43 | * @param image The Image to apply as shape's texture. 44 | * 45 | * @return YES on success, NO if texture loading fails. 46 | */ 47 | - (BOOL)setImage:(nullable UIImage *)image; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARImageShape.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARImageShape.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 12.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARImageShape.h" 27 | 28 | // GL data 29 | // 30 | typedef struct { 31 | 32 | float Position[3]; 33 | float Texture[2]; 34 | float Normal[3]; 35 | 36 | } Vertex; 37 | 38 | static const Vertex Vertices[] = { 39 | 40 | { { 0, +1, -1 }, { 1, 1 }, { 1, 0, 0 } }, 41 | { { 0, +1, +1 }, { 1, 0 }, { 1, 0, 0 } }, 42 | { { 0, -1, +1 }, { 0, 0 }, { 1, 0, 0 } }, 43 | { { 0, -1, -1 }, { 0, 1 }, { 1, 0, 0 } } 44 | }; 45 | 46 | static const GLubyte Indices[] = { 0, 1, 2, 2, 3, 0 }; 47 | 48 | static GLuint indexBuffer = 0; 49 | static NSUInteger bufferCount = 0; 50 | 51 | @interface TGLARImageShape () { 52 | 53 | GLuint _vertexBuffer; 54 | GLuint _indexBuffer; 55 | }; 56 | 57 | @property (strong, nonatomic) GLKTextureInfo *textureInfo; 58 | 59 | @end 60 | 61 | @implementation TGLARImageShape 62 | 63 | - (instancetype)initWithContext:(EAGLContext *)context size:(CGSize)size image:(UIImage *)image { 64 | 65 | self = [super initWithContext:context]; 66 | 67 | if (self) { 68 | 69 | self.effect.constantColor = GLKVector4Make(1.0, 1.0, 1.0, 1.0); 70 | 71 | self.image = image; 72 | 73 | float w2 = 0.5 * size.width; 74 | float h2 = 0.5 * size.height; 75 | 76 | static Vertex bgVertices[4]; 77 | 78 | for (NSUInteger idx = 0; idx < 4; idx++) { 79 | 80 | bgVertices[idx].Position[0] = Vertices[idx].Position[0]; 81 | bgVertices[idx].Position[1] = Vertices[idx].Position[1] * w2; 82 | bgVertices[idx].Position[2] = Vertices[idx].Position[2] * h2; 83 | 84 | bgVertices[idx].Texture[0] = Vertices[idx].Texture[0]; 85 | bgVertices[idx].Texture[1] = Vertices[idx].Texture[1]; 86 | 87 | bgVertices[idx].Normal[0] = Vertices[idx].Normal[0]; 88 | bgVertices[idx].Normal[1] = Vertices[idx].Normal[1]; 89 | bgVertices[idx].Normal[2] = Vertices[idx].Normal[2]; 90 | } 91 | 92 | glGenBuffers(1, &_vertexBuffer); 93 | glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); 94 | glBufferData(GL_ARRAY_BUFFER, sizeof(bgVertices), bgVertices, GL_STATIC_DRAW); 95 | glBindBuffer(GL_ARRAY_BUFFER, 0); 96 | 97 | if (indexBuffer == 0) { 98 | 99 | glGenBuffers(1, &indexBuffer); 100 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); 101 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW); 102 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 103 | } 104 | 105 | ++bufferCount; 106 | } 107 | 108 | return self; 109 | } 110 | 111 | - (void)dealloc { 112 | 113 | if (self.context) { 114 | 115 | [EAGLContext setCurrentContext:self.context]; 116 | 117 | [self freeImage]; 118 | 119 | glDeleteBuffers(1, &_vertexBuffer); _vertexBuffer = 0; 120 | 121 | if (bufferCount > 0) --bufferCount; 122 | 123 | if (bufferCount == 0) { 124 | 125 | glDeleteBuffers(1, &indexBuffer); 126 | indexBuffer = 0; 127 | } 128 | } 129 | } 130 | 131 | #pragma mark - Methods 132 | 133 | - (BOOL)setImage:(UIImage *)image { 134 | 135 | [EAGLContext setCurrentContext:self.context]; 136 | 137 | [self freeImage]; 138 | 139 | if (image == nil) return YES; 140 | 141 | NSError *error = nil; 142 | 143 | GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:nil error:&error]; 144 | 145 | if (textureInfo) { 146 | 147 | self.textureInfo = textureInfo; 148 | 149 | self.effect.texture2d0.name = self.textureInfo.name; 150 | self.effect.texture2d0.target = self.textureInfo.target; 151 | self.effect.texture2d0.envMode = GLKTextureEnvModeReplace; 152 | self.effect.texture2d0.enabled = GL_TRUE; 153 | 154 | return YES; 155 | 156 | } else { 157 | 158 | NSLog(@"%s Texture image could not be loaded: %@", __PRETTY_FUNCTION__, error.localizedDescription); 159 | 160 | self.effect.texture2d0.enabled = GL_FALSE; 161 | 162 | return NO; 163 | } 164 | } 165 | 166 | - (BOOL)draw { 167 | 168 | if (![super draw]) return NO; 169 | 170 | glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); 171 | glEnableVertexAttribArray(GLKVertexAttribPosition); 172 | glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, Position)); 173 | glEnableVertexAttribArray(GLKVertexAttribTexCoord0); 174 | glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, Texture)); 175 | glEnableVertexAttribArray(GLKVertexAttribNormal); 176 | glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, Normal)); 177 | 178 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); 179 | 180 | glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0); 181 | 182 | glDisableVertexAttribArray(GLKVertexAttribTexCoord0); 183 | glDisableVertexAttribArray(GLKVertexAttribNormal); 184 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 185 | glBindBuffer(GL_ARRAY_BUFFER, 0); 186 | 187 | return YES; 188 | } 189 | 190 | - (BOOL)drawUsingConstantColor:(GLKVector4)color { 191 | 192 | GLKVector4 constantColor = self.effect.constantColor; 193 | GLboolean useConstantColor = self.effect.useConstantColor; 194 | GLboolean texture2d0Enabled = self.effect.texture2d0.enabled; 195 | 196 | self.effect.constantColor = color; 197 | self.effect.useConstantColor = GL_TRUE; 198 | self.effect.texture2d0.enabled = GL_FALSE; 199 | 200 | BOOL ok = [super drawUsingConstantColor:color]; 201 | 202 | if (ok) { 203 | 204 | glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); 205 | glEnableVertexAttribArray(GLKVertexAttribPosition); 206 | glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, Position)); 207 | glEnableVertexAttribArray(GLKVertexAttribNormal); 208 | glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *)offsetof(Vertex, Normal)); 209 | 210 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); 211 | 212 | glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0); 213 | 214 | glDisableVertexAttribArray(GLKVertexAttribNormal); 215 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 216 | glBindBuffer(GL_ARRAY_BUFFER, 0); 217 | } 218 | 219 | self.effect.texture2d0.enabled = texture2d0Enabled; 220 | self.effect.useConstantColor = useConstantColor; 221 | self.effect.constantColor = constantColor; 222 | 223 | return ok; 224 | } 225 | 226 | #pragma mark - Helpers 227 | 228 | - (void)freeImage { 229 | 230 | if (self.textureInfo) { 231 | 232 | // KLUDGE: Get rid of previous texture memory, 233 | // but there seems to be no official 234 | // way to release a GLKTextureInfo 235 | // allocated by GLKTextureLoader... 236 | // 237 | // See: http://stackoverflow.com/a/8720298 238 | // 239 | GLuint name = self.textureInfo.name; 240 | 241 | glDeleteTextures(1, &name); 242 | 243 | self.textureInfo = nil; 244 | } 245 | 246 | self.effect.texture2d0.enabled = GL_FALSE; 247 | } 248 | 249 | @end 250 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLAROverlay.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLAROverlay.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | 29 | @class TGLARViewOverlay; 30 | @class TGLARShapeOverlay; 31 | 32 | /// An object returned by a @p TGLARViewDataSource must adopt this protocol. 33 | @protocol TGLAROverlay 34 | 35 | /** Returns the X/Y/Z position this overlay is attached to. 36 | * 37 | * The position is defined relative to the camera, which is 38 | * located at the coordinate system origin. The distance 39 | * values are given in meters. 40 | * 41 | * The coordinate system is right-handed with positive X axis 42 | * pointing north, the postive Y axis pointing east and the 43 | * positive Z axis pointing upwards. 44 | * 45 | * @return The X/Y/Z position of the overlay target. 46 | */ 47 | - (GLKVector3)targetPosition; 48 | 49 | /** Set the X/Y/Z position this overlay should be attached to. 50 | * 51 | * The distance values have to be given in meters. 52 | * 53 | * @param position The the overlay target X/Y/Z position. 54 | * 55 | * @sa -targetPosition 56 | */ 57 | - (void)setTargetPosition:(GLKVector3)position; 58 | 59 | @optional; 60 | 61 | /** Returns the view to show for this overlay. 62 | * 63 | * If the receiver does not respond to this selector 64 | * no view is shown for the overlay. 65 | * 66 | * @return A TGLARViewOverlay instance. 67 | * 68 | * @sa TGLARViewOverlay 69 | */ 70 | - (nullable TGLARViewOverlay *)overlayView; 71 | 72 | /** Returns the 3D shape to show for this overlay. 73 | * 74 | * If the receiver does not respond to this selector 75 | * no shape is shown for the overlay. 76 | * 77 | * @return A TGLARShapeOverlay instance. 78 | * 79 | * @sa TGLARShapeOverlay 80 | */ 81 | - (nullable TGLARShapeOverlay *)overlayShape; 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLAROverlayContainerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLAROverlayContainerView.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 17.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | 29 | #import "TGLARViewOverlay.h" 30 | 31 | /// A @p UIView subclass used internally to position the overlay shapes on a @p TGLARView. 32 | @interface TGLAROverlayContainerView : UIView 33 | 34 | /// The view containing the all of the overlay views. 35 | @property (nonatomic, readonly, nonnull) UIView *contentView; 36 | 37 | /// The current transformation derived from device attitude. 38 | @property (nonatomic, assign) GLKMatrix4 overlayTransformation; 39 | 40 | /// An array of @p TGLARViewOverlay objects to be layout out. 41 | @property (nonatomic, strong, nullable) NSArray *overlayViews; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLAROverlayContainerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLAROverlayContainerView.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 17.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLAROverlayContainerView.h" 27 | 28 | #import 29 | 30 | @implementation TGLAROverlayContainerView 31 | 32 | - (instancetype)initWithFrame:(CGRect)frame { 33 | 34 | self = [super initWithFrame:frame]; 35 | 36 | if (self) [self initContainer]; 37 | 38 | return self; 39 | } 40 | 41 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 42 | 43 | self = [super initWithCoder:aDecoder]; 44 | 45 | if (self) [self initContainer]; 46 | 47 | return self; 48 | } 49 | 50 | - (void)initContainer { 51 | 52 | _contentView = [[UIView alloc] init]; 53 | _contentView.backgroundColor = [UIColor clearColor]; 54 | _contentView.opaque = NO; 55 | 56 | [self addSubview:_contentView]; 57 | } 58 | 59 | #pragma mark - Accessors 60 | 61 | - (void)setOverlayViews:(NSArray *)overlayViews { 62 | 63 | for (TGLARViewOverlay *view in self.overlayViews) { 64 | 65 | [view removeFromSuperview]; 66 | } 67 | 68 | _overlayViews = overlayViews; 69 | 70 | [self setNeedsLayout]; 71 | } 72 | 73 | - (void)setOverlayTransformation:(GLKMatrix4)overlayTransformation { 74 | 75 | _overlayTransformation = overlayTransformation; 76 | 77 | [self setNeedsLayout]; 78 | } 79 | 80 | #pragma mark - Layout 81 | 82 | - (void)layoutSubviews { 83 | 84 | [super layoutSubviews]; 85 | 86 | CGRect bounds = self.bounds; 87 | CGSize offset = CGSizeZero; 88 | 89 | if (@available(iOS 11, *)) { 90 | 91 | CGRect safeBounds = CGRectMake(bounds.origin.x + self.safeAreaInsets.left, bounds.origin.y + self.safeAreaInsets.top, bounds.size.width - self.safeAreaInsets.left - self.safeAreaInsets.right, bounds.size.height - self.safeAreaInsets.top - self.safeAreaInsets.bottom);; 92 | 93 | self.contentView.frame = safeBounds; 94 | 95 | offset.width -= 0.5 * self.safeAreaInsets.left; 96 | offset.width += 0.5 * self.safeAreaInsets.right; 97 | 98 | offset.height -= 0.5 * self.safeAreaInsets.top; 99 | offset.height += 0.5 * self.safeAreaInsets.bottom; 100 | 101 | } else { 102 | 103 | self.contentView.frame = bounds; 104 | } 105 | 106 | // Perform 3D viewing transformation and clip invisible overlays 107 | // 108 | NSMutableArray *visibleViews = [NSMutableArray array]; 109 | 110 | for (TGLARViewOverlay *view in self.overlayViews) { 111 | 112 | GLKVector3 targetPosition = [view.overlay targetPosition]; 113 | GLKVector4 positionVector = GLKVector4MakeWithVector3(targetPosition, 1.0); 114 | GLKVector4 homoVector = GLKMatrix4MultiplyVector4(self.overlayTransformation, positionVector); 115 | 116 | view.viewPosition = GLKVector3Make(homoVector.x / homoVector.w, homoVector.y / homoVector.w, homoVector.z / homoVector.w); 117 | 118 | GLKVector2 unitPosition = GLKVector2Make(view.viewPosition.x, view.viewPosition.y); 119 | float unitLength = GLKVector2Length(unitPosition); 120 | 121 | if (unitLength < 2.0 && view.viewPosition.z <= 1.0) { 122 | 123 | [visibleViews addObject:view]; 124 | 125 | } else { 126 | 127 | view.hidden = YES; 128 | view.calloutLength = 0.0; 129 | } 130 | 131 | [view removeFromSuperview]; 132 | } 133 | 134 | // Arrange n visible overlays from back (0) to front (n-1) 135 | // 136 | [visibleViews sortUsingComparator:^NSComparisonResult (TGLARViewOverlay *view1, TGLARViewOverlay *view2) { 137 | 138 | if (view1.viewPosition.z > view2.viewPosition.z) return (NSComparisonResult)NSOrderedAscending; 139 | if (view1.viewPosition.z < view2.viewPosition.z) return (NSComparisonResult)NSOrderedDescending; 140 | 141 | return (NSComparisonResult)NSOrderedSame; 142 | }]; 143 | 144 | for (TGLARViewOverlay *view in visibleViews) [self.contentView addSubview:view]; 145 | 146 | // Position overlays in container and minimize overlap 147 | // 148 | CGFloat calloutLength = 0.0; 149 | CGFloat calloutOffset = 30.0; 150 | CGFloat calloutDefault = 120.0; 151 | 152 | for (NSInteger idx = visibleViews.count - 1; idx >= 0; idx--) { 153 | 154 | TGLARViewOverlay *view = visibleViews[idx]; 155 | 156 | GLKVector2 unitPosition = GLKVector2Make(view.viewPosition.x, view.viewPosition.y); 157 | float unitLength = GLKVector2Length(unitPosition); 158 | 159 | CGPoint screenPosition; 160 | 161 | screenPosition.x = round(0.5 * (unitPosition.x + 1.0) * CGRectGetWidth(self.contentView.bounds)) + offset.width; 162 | screenPosition.y = round(0.5 * (1.0 - unitPosition.y) * CGRectGetHeight(self.contentView.bounds)) + offset.height; 163 | 164 | if (view.hidden) { 165 | 166 | // Newly appearing view needs length 167 | // 168 | if (calloutLength > 0.0) { 169 | 170 | view.calloutLength = ++calloutLength; 171 | 172 | } else { 173 | 174 | view.calloutLength = calloutLength = calloutDefault; 175 | } 176 | 177 | view.rightAligned = (unitPosition.x > 0.0); 178 | view.hidden = NO; 179 | 180 | } else { 181 | 182 | if (calloutLength == 0.0) { 183 | 184 | if (view.calloutLength > calloutDefault) { 185 | 186 | view.calloutLength--; 187 | 188 | } else if (view.calloutLength < calloutDefault) { 189 | 190 | view.calloutLength++; 191 | } 192 | 193 | } else if ((view.calloutLength - calloutLength) > calloutOffset) { 194 | 195 | view.calloutLength--; 196 | 197 | } else if ((view.calloutLength - calloutLength) < calloutOffset) { 198 | 199 | view.calloutLength++; 200 | } 201 | 202 | calloutLength = view.calloutLength; 203 | } 204 | 205 | [view sizeToFit]; 206 | 207 | CGRect frame = view.bounds; 208 | 209 | frame.origin.x = screenPosition.x; 210 | 211 | if (view.rightAligned) frame.origin.x -= CGRectGetWidth(frame); 212 | 213 | frame.origin.y = screenPosition.y; 214 | 215 | if (!view.upsideDown) frame.origin.y -= CGRectGetHeight(frame); 216 | 217 | view.frame = frame; 218 | view.alpha = (unitLength > 1.0) ? MAX(2.0 - unitLength, 0.0) : 1.0; 219 | 220 | [view setNeedsDisplay]; 221 | } 222 | } 223 | 224 | #pragma mark - Interaction 225 | 226 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 227 | 228 | if ([self pointInside:point withEvent:event] && self.isUserInteractionEnabled && !self.isHidden && self.alpha > 0.01) { 229 | 230 | for (UIView *subview in [self.contentView.subviews reverseObjectEnumerator]) { 231 | 232 | CGPoint convertedPoint = [subview convertPoint:point fromView:self]; 233 | UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; 234 | 235 | if (hitTestView) return hitTestView; 236 | } 237 | 238 | return self; 239 | } 240 | 241 | return nil; 242 | } 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARShapeOverlay.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARShapeOverlay.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | 29 | #import "TGLAROverlay.h" 30 | 31 | /** An object to present 3D content in a @p TGLARView. 32 | * 33 | * The containing @p TGLARView sets the projection and 34 | * modelview matrices depending on the current device 35 | * orientation and attitude. 36 | * 37 | * A custom @p -transform is pre-multipled to the modelview 38 | * matrix to allow for individual shape tranformations before 39 | * the viewing transformations are applied. 40 | */ 41 | @interface TGLARShapeOverlay : NSObject 42 | 43 | /// The overlay this shape belongs to. 44 | @property (nonatomic, weak, nullable) id overlay; 45 | 46 | /// The shape's OpenGL ES rendering context. 47 | @property (nonatomic, weak, nullable) EAGLContext * context; 48 | /// The shape transformation matrix pre-multiplied to @p -viewMatrix. 49 | @property (nonatomic, assign) GLKMatrix4 transform; 50 | /// The shape's GLKKit rendering effect. @sa GLKBaseEffect for details. 51 | @property (nonatomic, readonly, nonnull) GLKBaseEffect *effect; 52 | 53 | /// The OpenGL ES view transformation to be applied. Set by the containing @p TGLARView. 54 | @property (nonatomic, assign) GLKMatrix4 viewMatrix; 55 | /// The OpenGL ES projection transformation to be applied. Set by the containing @p TGLARView. 56 | @property (nonatomic, assign) GLKMatrix4 projectionMatrix; 57 | 58 | /// Initialize an instance using the given OpenGL ES context. 59 | - (nullable instancetype)initWithContext:(nonnull EAGLContext *)context; 60 | 61 | /** Draws the shape using OpenGL ES. 62 | * 63 | * The method implementation initializes the transform property 64 | * of the @p -effect and calls its @p -prepareToDraw method. 65 | * 66 | * Override this method in subclasses to customize the shape. 67 | * Unless the subclass takes care of setting up the effect as 68 | * described above, the subcalls implementation @a must call 69 | * @p [super draw]. 70 | * 71 | * @return YES if drawing succeeds. NO otherwise, e.g. if -context is no longer valid. 72 | */ 73 | - (BOOL)draw; 74 | 75 | /** Draws the shape using OpenGL ES without any shading and texturing. 76 | * 77 | * The base class implementation simply calls @[self -draw]. 78 | * 79 | * This method is used internally to implement picking shapes 80 | * on an @p TGLARView. 81 | * 82 | * @sa @p TGLARView 83 | */ 84 | - (BOOL)drawUsingConstantColor:(GLKVector4)color; 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARShapeOverlay.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARShapeOverlay.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARShapeOverlay.h" 27 | 28 | @interface TGLARShapeOverlay () 29 | 30 | @property (nonatomic, strong) GLKTextureInfo *textureInfo; 31 | 32 | @end 33 | 34 | @implementation TGLARShapeOverlay 35 | 36 | - (instancetype)initWithContext:(EAGLContext *)context { 37 | 38 | self = [super init]; 39 | 40 | if (self) { 41 | 42 | _context = context; 43 | 44 | [EAGLContext setCurrentContext:self.context]; 45 | 46 | _effect = [[GLKBaseEffect alloc] init]; 47 | _transform = GLKMatrix4Identity; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | - (void)dealloc { 54 | 55 | if (self.context) { 56 | 57 | [EAGLContext setCurrentContext:self.context]; 58 | 59 | self.context = nil; 60 | } 61 | } 62 | 63 | #pragma mark - Methods 64 | 65 | - (BOOL)draw { 66 | 67 | if (!self.context) return NO; 68 | 69 | GLKVector3 targetPosition = self.overlay.targetPosition; 70 | GLKMatrix4 positionMatrix = GLKMatrix4MakeTranslation(targetPosition.x, targetPosition.y, targetPosition.z); 71 | GLKMatrix4 modelMatrix = GLKMatrix4Multiply(positionMatrix, self.transform); 72 | 73 | self.effect.transform.modelviewMatrix = GLKMatrix4Multiply(self.viewMatrix, modelMatrix); 74 | self.effect.transform.projectionMatrix = self.projectionMatrix; 75 | 76 | [self.effect prepareToDraw]; 77 | 78 | return YES; 79 | } 80 | 81 | - (BOOL)drawUsingConstantColor:(GLKVector4)color { 82 | 83 | return [self draw]; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARView.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import 28 | 29 | #import "TGLARCompass.h" 30 | #import "TGLAROverlay.h" 31 | 32 | @class TGLARView; 33 | 34 | /// The @pTGLARView data source must adopt the @p TGLARViewDataSource protocol. 35 | @protocol TGLARViewDataSource 36 | 37 | /** Asks the data source to return the number of overlays to be shown on the AR view. 38 | * 39 | * @param arview The AR view asking for the overlay count. 40 | * 41 | * @return The number of overlays to be shown on this particular AR view. 42 | */ 43 | - (NSInteger)numberOfOverlaysInARView:(nonnull TGLARView *)arview; 44 | 45 | /** Asks the data source for the overlay to be shown at a particular index. 46 | * 47 | * @param arview The AR view asking for an overlay. 48 | * 49 | * @return An object adopting the @p TGLAROverlay protocol. 50 | * 51 | * @sa @p TGLAROverlay 52 | */ 53 | - (nullable id)arView:(nonnull TGLARView *)arview overlayAtIndex:(NSInteger)index; 54 | 55 | @end 56 | 57 | /// The @pTGLARView delegate must adopt the @p TGLARViewDelegate protocol. 58 | @protocol TGLARViewDelegate 59 | 60 | @optional 61 | 62 | /** Tells the delegate that the user is about to change the offsets by a pan gesture on the AR view. 63 | * 64 | * @param arview The AR view informing the delegate about the beginning updates. 65 | * 66 | * @sa @p -heightOffset 67 | * @sa @p -headingOffset 68 | * @sa @p -positionOffset 69 | */ 70 | - (void)arViewWillBeginUpdatingOffsets:(nonnull TGLARView *)arview; 71 | 72 | /** Tells the delegate that the offset values properties have been updated during a pan gesture. 73 | * 74 | * @param arview The AR view informing the delegate about the offset value update. 75 | * 76 | * @sa @p -heightOffset 77 | * @sa @p -headingOffset 78 | * @sa @p -positionOffset 79 | */ 80 | - (void)arViewDidUpdateOffsets:(nonnull TGLARView *)arview; 81 | 82 | /** Tells the delegate that the user ended the pan gesture. 83 | * 84 | * @param arview The AR view informing the delegate about the end of the updates. 85 | * 86 | * @sa @p -heightOffset 87 | * @sa @p -headingOffset 88 | * @sa @p -positionOffset 89 | */ 90 | - (void)arViewDidEndUpdatingOffsets:(nonnull TGLARView *)arview; 91 | 92 | /** Tells the delegate that the user is about to change to video zoom by a pinch gesture on the AR view. 93 | * 94 | * @param arview The AR view informing the delegate about the beginning updates. 95 | * 96 | * @sa @p -zoomFactor 97 | */ 98 | - (void)arViewWillBeginZooming:(nonnull TGLARView *)arview; 99 | 100 | /** Tells the delegate that the video zoom factore has been updated during a pinch gesture. 101 | * 102 | * @param arview The AR view informing the delegate about the zoom factor update. 103 | * 104 | * @sa @p -zoomFactor 105 | */ 106 | - (void)arViewDidZoom:(nonnull TGLARView *)arview; 107 | 108 | /** Tells the delegate that the user ended the pinch gesture. 109 | * 110 | * @param arview The AR view informing the delegate about the end of the updates. 111 | * @param zoomFactor The final video zoom factor. 112 | * 113 | * @sa @p -zoomFactor 114 | */ 115 | - (void)arViewDidEndZooming:(nonnull TGLARView *)arview atFactor:(CGFloat)zoomFactor; 116 | 117 | /** Tells the delegate that the user tapped a particular overlay view. 118 | * 119 | * @param arview The AR view informing the delegate about the tap. 120 | * @param overlayView The view tapped by the user. 121 | */ 122 | - (void)arView:(nonnull TGLARView *)arview didTapViewOverlay:(nonnull TGLARViewOverlay *)overlayView; 123 | 124 | /** Tells the delegate that the user tapped a particular overlay shape. 125 | * 126 | * @param arview The AR view informing the delegate about the tap. 127 | * @param overlayShape The shape tapped by the user. 128 | */ 129 | - (void)arView:(nonnull TGLARView *)arview didTapShapeOverlay:(nonnull TGLARShapeOverlay *)overlayShape; 130 | 131 | /** Asks the delegate for the far clipping of overlay shapes. 132 | * 133 | * If this method is not implemented by the delegate, the value 134 | * defaults to 10000.0. 135 | * 136 | * @param arview The AR view requesting the distance value. 137 | * 138 | * @return A non-negative value for the far clipping distance. 139 | */ 140 | - (CGFloat)arViewShapeOverlayFarClippingDistance:(nonnull TGLARView *)arview; 141 | 142 | /** Asks the delegate for the near clipping of overlay shapes. 143 | * 144 | * If this method is not implemented by the delegate, the value 145 | * defaults to 1.0. 146 | * 147 | * @param arview The AR view requesting the distance value. 148 | * 149 | * @return A non-negative value for the near clipping distance. 150 | */ 151 | - (CGFloat)arViewShapeOverlayNearClippingDistance:(nonnull TGLARView *)arview; 152 | 153 | @end 154 | 155 | /** The @p TGLARView presents 2D view-based overlays and 3D shape overlays on top of a camera preview. 156 | * 157 | * Overlays are virtually attached to their @p -targetPosition. They are positioned in the @p TGLARView 158 | * depending on the current device attitude as if seen through the device's back-facing camera. 159 | * 160 | * @p TGLARView uses @p CoreMotion to get the device orientation in X/Y/Z space, where positive X is north, 161 | * positive Y west and Z points up. If supported by the device the X axis is aligned to magnetic north. 162 | * 163 | * The user may use a horizontal pan gesture to adjust the X/Y plane alignment, e.g. when magnetic north 164 | * is not available on the device. A vertical pan is used to adjust virtual camera height. By default, the 165 | * camera is located at the origin @p (0,0,0). 166 | * 167 | * If video zoom is suppoted for the current video format, a pinch gesture may be used to adjust the 168 | * zoom factor. The virtual camera field of view is adjusted automatically when zooming. 169 | */ 170 | @interface TGLARView : UIView 171 | 172 | /// An object conforming to @p TGLARCompass protocol receiving heading updates while the device moves. Default is @p nil. 173 | @property (nonatomic, weak, nullable) IBOutlet id compass; 174 | /// The object that acts as the delegate of this AR view. Default is @p nil. 175 | @property (nonatomic, weak, nullable) IBOutlet id delegate; 176 | /// The object that acts as the data source of this AR view. Default is @p nil. 177 | @property (nonatomic, weak, nullable) IBOutlet id dataSource; 178 | 179 | /// Tells the AR view which device orientation to assume. Controls the selection of the virtual camera's field of view. 180 | @property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation; 181 | 182 | /// Current video zoom factor from @p 1.0 to @p -maxZoomFactor. Default is @p 1.0. 183 | @property (nonatomic, assign) CGFloat zoomFactor; 184 | /// Maximum video zoom factor. @p 1.0 means no zoom. 185 | @property (nonatomic, readonly) CGFloat maxZoomFactor; 186 | 187 | /// Indicates whether CoreMotion can use compass on the current device 188 | @property (nonatomic, readonly, getter=isMagenticNorthAvailable) BOOL magneticNorthAvailable; 189 | /// If compass and location are available use true north heading. Default is @p NO. 190 | @property (nonatomic, assign, getter=isUsingTrueNorth) BOOL useTrueNorth; 191 | 192 | /** Camera height above X/Y plane in meters. Default is @p 0.0. 193 | * 194 | * The user may change this value by a vertical pan gesture. 195 | * 196 | * @sa @p TGLARViewDelegate 197 | */ 198 | @property (nonatomic, assign) CGFloat heightOffset; 199 | 200 | /** Camera heading angle in degrees added to device attitude. Default is @p 0.0. 201 | * 202 | * Positive angles result in a clockwise camera rotation. 203 | * The user may change this value by a horizontal pan gesture. 204 | * 205 | * @sa @p TGLARViewDelegate 206 | */ 207 | @property (nonatomic, assign) CGFloat headingOffset; 208 | 209 | /** Camera position offset in meters in the X/Y plane. 210 | * 211 | * @param positionOffset.width X axis offset 212 | * @param positionOffset.height Y axis offset 213 | * 214 | * Positive X values move the camera north, negative ones south. 215 | * Positive Y values move the camera west, negative ones east. 216 | */ 217 | @property (nonatomic, assign) CGSize positionOffset; 218 | 219 | /// Returns the OpenGL ES context used to draw overlay shapes. 220 | - (nonnull EAGLContext *)renderContext; 221 | 222 | /// Starts the video preview and rendering of the overlays. 223 | - (void)start; 224 | /// Stops the video preview and rendering of the overlays. 225 | - (void)stop; 226 | 227 | /** Reloads the overlays from the current data source. 228 | * 229 | * @sa @p -dataSource 230 | */ 231 | - (void)reloadData; 232 | 233 | @end 234 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARView.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 09.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARView.h" 27 | #import "TGLAROverlay.h" 28 | #import "TGLARViewOverlay.h" 29 | #import "TGLARShapeOverlay.h" 30 | #import "TGLAROverlayContainerView.h" 31 | #import "TGLARCompassView.h" 32 | 33 | #import 34 | #import 35 | #import 36 | 37 | static char FOVARViewKVOContext; 38 | 39 | static const CGFloat kFOVARViewLensAdjustmentFactor = 0.05; 40 | 41 | @interface TGLARView () { 42 | 43 | GLKMatrix4 _deviceTransform; 44 | GLKMatrix4 _cameraTransform; 45 | GLKMatrix4 _userTransformation; 46 | 47 | GLKMatrix4 _viewMatrix; 48 | GLKMatrix4 _projectionMatrix; 49 | } 50 | 51 | @property (nonatomic, strong) CMMotionManager *motionManager; 52 | 53 | @property (nonatomic, strong) UIView *captureView; 54 | @property (nonatomic, strong) AVCaptureDevice *captureDevice; 55 | @property (nonatomic, strong) AVCaptureSession *captureSession; 56 | @property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureLayer; 57 | 58 | @property (nonatomic, strong) GLKView *renderView; 59 | @property (nonatomic, strong) EAGLContext *renderContext; 60 | 61 | @property (nonatomic, strong) TGLAROverlayContainerView *containerView; 62 | 63 | @property (nonatomic, strong) CADisplayLink *displayLink; 64 | 65 | @property (nonatomic, assign) CGFloat fovScalePortrait; 66 | @property (nonatomic, assign) CGFloat fovScaleLandscape; 67 | 68 | @property (nonatomic, assign) CGFloat verticalFovPortrait; 69 | @property (nonatomic, assign) CGFloat verticalFovLandscape; 70 | 71 | @property (nonatomic, strong) NSArray *overlayShapes; 72 | 73 | @property (nonatomic, strong) UITapGestureRecognizer *tapRecognizer; 74 | @property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer; 75 | @property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer; 76 | 77 | @end 78 | 79 | #pragma mark - ARView implementation 80 | 81 | @implementation TGLARView 82 | 83 | @dynamic maxZoomFactor; 84 | 85 | - (instancetype)initWithFrame:(CGRect)frame { 86 | 87 | self = [super initWithFrame:frame]; 88 | 89 | if (self) [self initARView]; 90 | 91 | return self; 92 | } 93 | 94 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 95 | 96 | self = [super initWithCoder:aDecoder]; 97 | 98 | if (self) [self initARView]; 99 | 100 | return self; 101 | } 102 | 103 | - (void)initARView { 104 | 105 | _magneticNorthAvailable = [CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXMagneticNorthZVertical; 106 | _useTrueNorth = NO; 107 | 108 | _deviceTransform = GLKMatrix4Identity; 109 | _cameraTransform = GLKMatrix4Identity; 110 | _userTransformation = GLKMatrix4Identity; 111 | 112 | self.fovScalePortrait = 1.0; 113 | self.fovScaleLandscape = 1.0; 114 | 115 | // Make camera preview in background 116 | // 117 | self.captureView = [[UIView alloc] initWithFrame:self.bounds]; 118 | 119 | [self addSubview:self.captureView]; 120 | [self sendSubviewToBack:self.captureView]; 121 | 122 | // Make transparent GL view above preview 123 | // 124 | self.renderContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; 125 | 126 | self.renderView = [[GLKView alloc] initWithFrame:self.bounds context:self.renderContext]; 127 | self.renderView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; 128 | self.renderView.drawableDepthFormat = GLKViewDrawableDepthFormat24; 129 | self.renderView.delegate = self; 130 | 131 | CAEAGLLayer *renderLayer = (CAEAGLLayer *)self.renderView.layer; 132 | 133 | renderLayer.opaque = NO; 134 | 135 | self.renderView.backgroundColor = [UIColor clearColor]; 136 | self.renderView.opaque = NO; 137 | 138 | [self insertSubview:self.renderView aboveSubview:self.captureView]; 139 | 140 | // Make transparent overlay container above GL view 141 | // 142 | self.containerView = [[TGLAROverlayContainerView alloc] initWithFrame:self.bounds]; 143 | self.containerView.backgroundColor = [UIColor clearColor]; 144 | self.containerView.opaque = NO; 145 | 146 | [self insertSubview:self.containerView aboveSubview:self.renderView]; 147 | 148 | // Add gesture recognizers 149 | // 150 | self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; 151 | self.tapRecognizer.delegate = self; 152 | 153 | [self addGestureRecognizer:self.tapRecognizer]; 154 | 155 | self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; 156 | self.panRecognizer.delegate = self; 157 | 158 | [self addGestureRecognizer:self.panRecognizer]; 159 | 160 | self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; 161 | self.pinchRecognizer.delegate = self; 162 | 163 | [self addGestureRecognizer:self.pinchRecognizer]; 164 | } 165 | 166 | - (void)dealloc { 167 | 168 | [self stop]; 169 | 170 | self.overlayShapes = nil; 171 | 172 | [self.captureView removeFromSuperview]; 173 | [self.renderView removeFromSuperview]; 174 | 175 | if ([EAGLContext currentContext] == self.renderContext) { 176 | 177 | [EAGLContext setCurrentContext:nil]; 178 | } 179 | 180 | self.renderContext = nil; 181 | } 182 | 183 | #pragma mark - Layout 184 | 185 | - (void)layoutSubviews { 186 | 187 | [super layoutSubviews]; 188 | 189 | [self computeFovFromCameraFormat]; 190 | [self updateProjectionMatrix]; 191 | 192 | CGRect bounds = self.bounds; 193 | 194 | self.captureView.frame = bounds; 195 | self.captureLayer.frame = bounds; 196 | self.renderView.frame = bounds; 197 | self.containerView.frame = bounds; 198 | } 199 | 200 | #pragma mark - Accessors 201 | 202 | - (void)setInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 203 | 204 | _interfaceOrientation = interfaceOrientation; 205 | 206 | switch (self.interfaceOrientation) { 207 | 208 | case UIInterfaceOrientationPortrait: 209 | 210 | _deviceTransform = GLKMatrix4Identity; 211 | if (self.captureLayer.connection.isVideoOrientationSupported) [self.captureLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait]; 212 | break; 213 | 214 | case UIInterfaceOrientationPortraitUpsideDown: 215 | 216 | _deviceTransform = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(180.0), 0, 0, 1); 217 | if (self.captureLayer.connection.isVideoOrientationSupported) [self.captureLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown]; 218 | break; 219 | 220 | case UIInterfaceOrientationLandscapeLeft: 221 | 222 | _deviceTransform = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(-90.0), 0, 0, 1); 223 | if (self.captureLayer.connection.isVideoOrientationSupported) [self.captureLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft]; 224 | break; 225 | 226 | case UIInterfaceOrientationLandscapeRight: 227 | 228 | _deviceTransform = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(+90.0), 0, 0, 1); 229 | if (self.captureLayer.connection.isVideoOrientationSupported) [self.captureLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight]; 230 | break; 231 | 232 | default: 233 | 234 | break; 235 | } 236 | 237 | [self updateProjectionMatrix]; 238 | } 239 | 240 | - (void)setZoomFactor:(CGFloat)zoomFactor { 241 | 242 | if (self.captureDevice) { 243 | 244 | NSError *error = nil; 245 | 246 | if ([self.captureDevice lockForConfiguration:&error]) { 247 | 248 | self.captureDevice.videoZoomFactor = MAX(1.0, MIN(zoomFactor, self.maxZoomFactor)); 249 | 250 | [self.captureDevice unlockForConfiguration]; 251 | 252 | } else { 253 | 254 | NSLog(@"%s ERROR: %@", __PRETTY_FUNCTION__, error.localizedDescription); 255 | } 256 | 257 | _zoomFactor = self.captureDevice.videoZoomFactor; 258 | 259 | } else { 260 | 261 | _zoomFactor = zoomFactor; 262 | } 263 | 264 | [self computeFovFromCameraFormat]; 265 | [self updateProjectionMatrix]; 266 | } 267 | 268 | - (CGFloat)maxZoomFactor { 269 | 270 | return self.captureDevice.activeFormat.videoMaxZoomFactor; 271 | } 272 | 273 | - (void)setUseTrueNorth:(BOOL)useTrueNorth { 274 | 275 | if (useTrueNorth != _useTrueNorth && self.isMagenticNorthAvailable) { 276 | 277 | if (useTrueNorth && [CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) { 278 | 279 | _useTrueNorth = YES; 280 | 281 | [self restartDeviceMotionIfNecessary]; 282 | 283 | } else if (!useTrueNorth) { 284 | 285 | _useTrueNorth = NO; 286 | 287 | [self restartDeviceMotionIfNecessary]; 288 | } 289 | } 290 | } 291 | 292 | - (void)setHeightOffset:(CGFloat)offset { 293 | 294 | if (offset != _heightOffset) { 295 | 296 | _heightOffset = offset; 297 | 298 | [self updateUserTransformation]; 299 | } 300 | } 301 | 302 | - (void)setHeadingOffset:(CGFloat)offset { 303 | 304 | if (offset != _headingOffset) { 305 | 306 | _headingOffset = offset; 307 | 308 | [self updateUserTransformation]; 309 | } 310 | } 311 | 312 | - (void)setPositionOffset:(CGSize)offset { 313 | 314 | if (!CGSizeEqualToSize(offset, _positionOffset)) { 315 | 316 | _positionOffset = offset; 317 | 318 | [self updateUserTransformation]; 319 | } 320 | } 321 | 322 | #pragma mark - Actions 323 | 324 | - (IBAction)handleTapGesture:(UITapGestureRecognizer *)recognizer { 325 | 326 | CGPoint containerTap = [recognizer locationInView:self.containerView]; 327 | 328 | UIView *view = [self.containerView hitTest:containerTap withEvent:nil]; 329 | 330 | if (view && view != self.containerView) { 331 | 332 | if ([self.delegate respondsToSelector:@selector(arView:didTapViewOverlay:)]) { 333 | 334 | [self.delegate arView:self didTapViewOverlay:(TGLARViewOverlay *)view]; 335 | } 336 | 337 | } else { 338 | 339 | CGPoint renderTap = [recognizer locationInView:self.renderView]; 340 | TGLARShapeOverlay *shape = [self findShapeAtPoint:renderTap]; 341 | 342 | if (shape && [self.delegate respondsToSelector:@selector(arView:didTapShapeOverlay:)]) { 343 | 344 | [self.delegate arView:self didTapShapeOverlay:shape]; 345 | } 346 | } 347 | } 348 | 349 | #define PAN_UNLOCKED 0 350 | #define PAN_LOCK_H 1 351 | #define PAN_LOCK_V 2 352 | 353 | - (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer { 354 | 355 | static CGPoint start; 356 | static short lock; 357 | static float heightOffset; 358 | static float headingOffset; 359 | 360 | if (recognizer.state == UIGestureRecognizerStateBegan) { 361 | 362 | start = [recognizer locationInView:self]; 363 | lock = PAN_UNLOCKED; 364 | heightOffset = self.heightOffset; 365 | headingOffset = self.headingOffset; 366 | 367 | if ([self.delegate respondsToSelector:@selector(arViewWillBeginUpdatingOffsets:)]) { 368 | 369 | [self.delegate arViewWillBeginUpdatingOffsets:self]; 370 | } 371 | 372 | } else if (recognizer.state == UIGestureRecognizerStateChanged) { 373 | 374 | CGPoint touch = [recognizer locationInView:self]; 375 | 376 | float deltaX = (touch.x - start.x); 377 | float deltaY = (touch.y - start.y); 378 | 379 | if (lock == PAN_UNLOCKED) { 380 | 381 | lock = (fabs(deltaX) > fabs(deltaY)) ? PAN_LOCK_H : PAN_LOCK_V; 382 | } 383 | 384 | if (lock == PAN_LOCK_H) { 385 | 386 | float deltaHeading = deltaX / self.bounds.size.width; 387 | 388 | self.headingOffset = headingOffset - deltaHeading * [self effectiveHorizontalFov]; 389 | 390 | [self updateUserTransformation]; 391 | 392 | } else if (lock == PAN_LOCK_V) { 393 | 394 | float deltaHeight = 0.01 * deltaY; 395 | float newHeightOffset = heightOffset + deltaHeight; 396 | 397 | self.heightOffset = newHeightOffset; 398 | 399 | [self updateUserTransformation]; 400 | } 401 | 402 | if ([self.delegate respondsToSelector:@selector(arViewDidUpdateOffsets:)]) { 403 | 404 | [self.delegate arViewDidUpdateOffsets:self]; 405 | } 406 | 407 | } else if (recognizer.state == UIGestureRecognizerStateCancelled || recognizer.state == UIGestureRecognizerStateEnded) { 408 | 409 | if ([self.delegate respondsToSelector:@selector(arViewDidEndUpdatingOffsets:)]) { 410 | 411 | [self.delegate arViewDidEndUpdatingOffsets:self]; 412 | } 413 | } 414 | } 415 | 416 | - (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer { 417 | 418 | static CGFloat start; 419 | 420 | if (recognizer.state == UIGestureRecognizerStateBegan) { 421 | 422 | start = self.zoomFactor; 423 | 424 | if ([self.delegate respondsToSelector:@selector(arViewWillBeginZooming:)]) { 425 | 426 | [self.delegate arViewWillBeginZooming:self]; 427 | } 428 | 429 | } else if (recognizer.state == UIGestureRecognizerStateChanged) { 430 | 431 | self.zoomFactor = start * recognizer.scale; 432 | 433 | if ([self.delegate respondsToSelector:@selector(arViewDidZoom:)]) { 434 | 435 | [self.delegate arViewDidZoom:self]; 436 | } 437 | 438 | } else if (recognizer.state == UIGestureRecognizerStateCancelled || recognizer.state == UIGestureRecognizerStateEnded) { 439 | 440 | if ([self.delegate respondsToSelector:@selector(arViewDidEndZooming:atFactor:)]) { 441 | 442 | [self.delegate arViewDidEndZooming:self atFactor:self.zoomFactor]; 443 | } 444 | } 445 | } 446 | 447 | #pragma mark - Methods 448 | 449 | - (void)start { 450 | 451 | [self startCameraPreview]; 452 | [self startDeviceMotion]; 453 | [self startDisplayLink]; 454 | 455 | [self setNeedsLayout]; 456 | } 457 | 458 | - (void)stop { 459 | 460 | [self stopDisplayLink]; 461 | [self stopDeviceMotion]; 462 | [self stopCameraPreview]; 463 | } 464 | 465 | - (void)reloadData { 466 | 467 | NSMutableArray *overlayViews = [NSMutableArray array]; 468 | NSMutableArray *overlayShapes = [NSMutableArray array]; 469 | 470 | NSInteger count = [self.dataSource numberOfOverlaysInARView:self]; 471 | 472 | for (NSInteger index = 0; index < count; index++) { 473 | 474 | id overlay = [self.dataSource arView:self overlayAtIndex:index]; 475 | 476 | if ([overlay respondsToSelector:@selector(overlayView)]) { 477 | 478 | TGLARViewOverlay *view = overlay.overlayView; 479 | 480 | if (view) [overlayViews addObject:view]; 481 | } 482 | 483 | if ([overlay respondsToSelector:@selector(overlayShape)]) { 484 | 485 | TGLARShapeOverlay *shape = overlay.overlayShape; 486 | 487 | if (shape) [overlayShapes addObject:shape]; 488 | } 489 | } 490 | 491 | self.overlayShapes = overlayShapes; 492 | self.containerView.overlayViews = overlayViews; 493 | } 494 | 495 | #pragma mark - Camera handling 496 | 497 | - (void)startCameraPreview { 498 | 499 | AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 500 | 501 | if (camera == nil) return; 502 | 503 | self.captureDevice = camera; 504 | 505 | // Apply zoom factor to new camera 506 | // 507 | self.zoomFactor = self.zoomFactor; 508 | 509 | // Register for focus changes to adjust field of view 510 | // 511 | [self.captureDevice addObserver:self forKeyPath:@"lensPosition" options:NSKeyValueObservingOptionInitial context:&FOVARViewKVOContext]; 512 | 513 | self.captureSession = [[AVCaptureSession alloc] init]; 514 | self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; 515 | 516 | AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.captureDevice error:nil]; 517 | 518 | [self.captureSession addInput:newVideoInput]; 519 | 520 | self.captureLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession]; 521 | self.captureLayer.frame = self.captureView.bounds; 522 | self.captureLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 523 | 524 | [self.captureView.layer addSublayer:self.captureLayer]; 525 | 526 | // Start the session. 527 | // 528 | // This is done asychronously since -startRunning 529 | // doesn't return until the session is running. 530 | // 531 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 532 | 533 | [self.captureSession startRunning]; 534 | }); 535 | } 536 | 537 | - (void)stopCameraPreview { 538 | 539 | [self.captureSession stopRunning]; 540 | [self.captureLayer removeFromSuperlayer]; 541 | 542 | self.captureLayer = nil; 543 | self.captureSession = nil; 544 | 545 | [self.captureDevice removeObserver:self forKeyPath:@"lensPosition"]; 546 | 547 | self.captureDevice = nil; 548 | } 549 | 550 | #pragma mark - Motion handling 551 | 552 | - (void)startDeviceMotion { 553 | 554 | self.motionManager = [[CMMotionManager alloc] init]; 555 | self.motionManager.showsDeviceMovementDisplay = YES; 556 | self.motionManager.deviceMotionUpdateInterval = 1.0 / 60.0; 557 | 558 | // When available use compass to have x axis pointing to magnetic north 559 | // 560 | CMAttitudeReferenceFrame referenceFrame; 561 | 562 | if (self.isMagenticNorthAvailable) { 563 | 564 | referenceFrame = self.isUsingTrueNorth ? CMAttitudeReferenceFrameXTrueNorthZVertical : CMAttitudeReferenceFrameXMagneticNorthZVertical; 565 | 566 | } else { 567 | 568 | referenceFrame = CMAttitudeReferenceFrameXArbitraryZVertical; 569 | } 570 | 571 | [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:referenceFrame]; 572 | } 573 | 574 | - (void)stopDeviceMotion { 575 | 576 | [self.motionManager stopDeviceMotionUpdates]; 577 | 578 | self.motionManager = nil; 579 | } 580 | 581 | - (void)restartDeviceMotionIfNecessary { 582 | 583 | if (self.motionManager) { 584 | 585 | [self stopDeviceMotion]; 586 | [self startDeviceMotion]; 587 | } 588 | } 589 | 590 | #pragma mark - Redraw handling 591 | 592 | - (void)startDisplayLink { 593 | 594 | self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)]; 595 | 596 | [self.displayLink setFrameInterval:1]; 597 | [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 598 | } 599 | 600 | - (void)stopDisplayLink { 601 | 602 | [self.displayLink invalidate]; 603 | 604 | self.displayLink = nil; 605 | } 606 | 607 | - (void)onDisplayLink:(id)sender { 608 | 609 | CMDeviceMotion *d = self.motionManager.deviceMotion; 610 | 611 | if (d != nil) { 612 | 613 | CMRotationMatrix r = d.attitude.rotationMatrix; 614 | 615 | _cameraTransform = GLKMatrix4Make(r.m11, r.m21, r.m31, 0.0, r.m12, r.m22, r.m32, 0.0, r.m13, r.m23, r.m33, 0.0, 0.0, 0.0, 0.0, 1.0); 616 | } 617 | 618 | // Trigger -glkView:drawInRect: 619 | // 620 | [self.renderView setNeedsDisplay]; 621 | } 622 | 623 | - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { 624 | 625 | // Compute modelview and projection matrices 626 | // and use them to transform GL overlay shapes 627 | // as well as overlay views and compass 628 | // 629 | GLKMatrix4 cameraMatrix = GLKMatrix4Multiply(_cameraTransform, _userTransformation); 630 | 631 | _viewMatrix = GLKMatrix4Multiply(_deviceTransform, cameraMatrix); 632 | 633 | [self drawShapes:NO]; 634 | 635 | self.containerView.overlayTransformation = GLKMatrix4Multiply(_projectionMatrix, _viewMatrix); 636 | 637 | if (self.compass) { 638 | 639 | bool inverted; 640 | 641 | GLKMatrix4 inverseView = GLKMatrix4Invert(_viewMatrix, &inverted); 642 | 643 | if (inverted) { 644 | 645 | GLKVector3 xAxis = GLKVector3Make(1, 0, 0); 646 | GLKVector3 northAxis = GLKMatrix4MultiplyVector3(inverseView, xAxis); 647 | 648 | northAxis.z = 0.0; 649 | northAxis = GLKVector3Normalize(northAxis); 650 | 651 | float northDot = GLKVector3DotProduct(northAxis, xAxis); 652 | float northAngle = GLKMathRadiansToDegrees(acosf(northDot)); 653 | 654 | if (northAxis.y > 0.0) northAngle = 360.0 - northAngle; 655 | 656 | northAngle -= 90.0; 657 | 658 | if (northAngle < 0.0) northAngle += 360.0; 659 | 660 | [self.compass setHeadingAngle:northAngle]; 661 | } 662 | } 663 | } 664 | 665 | - (void)drawShapes:(BOOL)picking { 666 | 667 | glEnable(GL_DEPTH_TEST); 668 | glEnable(GL_CULL_FACE); 669 | 670 | if (picking) { 671 | 672 | glClearColor(1.0f, 1.0f, 1.0f, picking ? 1.0f : 0.0f); 673 | 674 | } else { 675 | 676 | glClearColor(0.0f, 0.0f, 0.0f, picking ? 1.0f : 0.0f); 677 | } 678 | 679 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 680 | 681 | for (NSInteger idx = 0; idx < self.overlayShapes.count; idx++) { 682 | 683 | TGLARShapeOverlay *shape = self.overlayShapes[idx]; 684 | 685 | shape.viewMatrix = _viewMatrix; 686 | shape.projectionMatrix = _projectionMatrix; 687 | 688 | if (picking) { 689 | 690 | // TODO: idx > 254 691 | // 692 | [shape drawUsingConstantColor:GLKVector4Make((idx + 1) / 255.0f, 0.0f, 0.0f, 0.0f)]; 693 | 694 | } else { 695 | 696 | [shape draw]; 697 | } 698 | } 699 | } 700 | 701 | #pragma mark - Pick handling 702 | 703 | // See http://stackoverflow.com/a/10784181 704 | 705 | - (TGLARShapeOverlay *)findShapeAtPoint:(CGPoint)point { 706 | 707 | [EAGLContext setCurrentContext:self.renderContext]; 708 | 709 | NSInteger height = self.renderView.drawableHeight; 710 | NSInteger width = self.renderView.drawableWidth; 711 | 712 | GLuint framebuffer; 713 | 714 | glGenFramebuffers(1, &framebuffer); 715 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); 716 | 717 | GLuint colorRenderbuffer; 718 | 719 | glGenRenderbuffers(1, &colorRenderbuffer); 720 | glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer); 721 | 722 | glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, (GLsizei)width, (GLsizei)height); 723 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer); 724 | 725 | GLuint depthRenderbuffer; 726 | 727 | glGenRenderbuffers(1, &depthRenderbuffer); 728 | glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer); 729 | 730 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, (GLsizei)width, (GLsizei)height); 731 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer); 732 | 733 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 734 | 735 | if (status != GL_FRAMEBUFFER_COMPLETE) { 736 | 737 | NSLog(@"Framebuffer status: %x", (int)status); 738 | return nil; 739 | } 740 | 741 | [self drawShapes:YES]; 742 | 743 | Byte pixelColor[4] = {0,}; 744 | CGFloat scale = UIScreen.mainScreen.scale; 745 | 746 | glReadPixels(point.x * scale, (height - (point.y * scale)), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixelColor); 747 | 748 | //NSLog(@"%s Pixel color @ %@: %x %x %x %x", __PRETTY_FUNCTION__, NSStringFromCGPoint(point), pixelColor[0], pixelColor[1], pixelColor[2], pixelColor[3]); 749 | 750 | glDeleteRenderbuffers(1, &depthRenderbuffer); 751 | glDeleteRenderbuffers(1, &colorRenderbuffer); 752 | glDeleteFramebuffers(1, &framebuffer); 753 | 754 | [self.renderView bindDrawable]; 755 | 756 | NSInteger idx = pixelColor[0]; 757 | 758 | return (idx > 0 && idx <= self.overlayShapes.count) ? self.overlayShapes[idx-1] : nil; 759 | } 760 | 761 | #pragma mark - Projection matrix handling 762 | 763 | - (void)computeFovFromCameraFormat { 764 | 765 | if (self.captureDevice) { 766 | 767 | CGFloat aspectRatio = self.bounds.size.width / self.bounds.size.height; 768 | 769 | if (aspectRatio > 1.0) aspectRatio = 1.0 / aspectRatio; 770 | 771 | AVCaptureDeviceFormat *activeFormat = self.captureDevice.activeFormat; 772 | CGFloat activeFOV = GLKMathRadiansToDegrees(2.0 * atan(tan(0.5 * GLKMathDegreesToRadians(activeFormat.videoFieldOfView)) / self.captureDevice.videoZoomFactor)); 773 | 774 | CMFormatDescriptionRef description = activeFormat.formatDescription; 775 | CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(description); 776 | 777 | CGFloat aspectWidth = (CGFloat)dimensions.height / aspectRatio; 778 | CGFloat aspectHeight = (CGFloat)dimensions.width * aspectRatio; 779 | 780 | CGFloat aspectFOV; 781 | 782 | if (aspectWidth < dimensions.width) { 783 | 784 | aspectFOV = GLKMathRadiansToDegrees(2.0 * atan(aspectWidth / (CGFloat)dimensions.width * tan(0.5 * GLKMathDegreesToRadians(activeFOV)))); 785 | 786 | } else if (aspectHeight < dimensions.height) { 787 | 788 | aspectFOV = activeFOV; 789 | 790 | } else { 791 | 792 | aspectFOV = activeFOV; 793 | } 794 | 795 | self.verticalFovPortrait = aspectFOV; 796 | self.verticalFovLandscape = GLKMathRadiansToDegrees(2.0 * atan(tan(0.5 * GLKMathDegreesToRadians(aspectFOV)) * aspectRatio)); 797 | 798 | if ([self.compass respondsToSelector:@selector(setFieldOfView:)]) { 799 | 800 | [self.compass setFieldOfView:self.effectiveHorizontalFov]; 801 | } 802 | } 803 | } 804 | 805 | - (void)updateProjectionMatrix { 806 | 807 | // Initialize camera & projection matrix 808 | // 809 | CGFloat fovy = self.effectiveVerticalFov; 810 | CGFloat aspect = self.bounds.size.width / self.bounds.size.height; 811 | 812 | CGFloat near = ([self.delegate respondsToSelector:@selector(arViewShapeOverlayNearClippingDistance:)]) ? [self.delegate arViewShapeOverlayNearClippingDistance:self] : 1.0; 813 | CGFloat far = ([self.delegate respondsToSelector:@selector(arViewShapeOverlayFarClippingDistance:)]) ? [self.delegate arViewShapeOverlayFarClippingDistance:self] : 10000.0; 814 | 815 | _projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(fovy), aspect, near, far); 816 | } 817 | 818 | - (void)updateUserTransformation { 819 | 820 | GLKMatrix4 rotation = GLKMatrix4MakeRotation(self.headingOffset / 180.0 * M_PI, 0.0, 0.0, 1.0); 821 | GLKMatrix4 translation = GLKMatrix4MakeTranslation(-self.positionOffset.width, -self.positionOffset.height, -self.heightOffset); 822 | 823 | _userTransformation = GLKMatrix4Multiply(rotation, translation); 824 | } 825 | 826 | #pragma mark - Key-value Observing 827 | 828 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 829 | 830 | if (context == &FOVARViewKVOContext && [keyPath isEqualToString:@"lensPosition"]) { 831 | 832 | CGFloat scale = 1.0 + self.captureDevice.lensPosition * kFOVARViewLensAdjustmentFactor; 833 | 834 | self.fovScalePortrait = self.fovScaleLandscape = scale; 835 | 836 | } else if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) { 837 | 838 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 839 | } 840 | } 841 | 842 | #pragma mark - Helpers 843 | 844 | - (CGFloat)effectiveVerticalFov { 845 | 846 | if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { 847 | 848 | return self.fovScalePortrait * self.verticalFovPortrait; 849 | 850 | } else { 851 | 852 | return self.fovScaleLandscape * self.verticalFovLandscape; 853 | } 854 | } 855 | 856 | - (CGFloat)effectiveHorizontalFov { 857 | 858 | if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { 859 | 860 | return self.fovScalePortrait * self.verticalFovLandscape; 861 | 862 | } else { 863 | 864 | return self.fovScaleLandscape * self.verticalFovPortrait; 865 | } 866 | } 867 | 868 | @end 869 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARViewOverlay.h: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARViewOverlay.h 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 13.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #import "TGLAROverlay.h" 29 | 30 | /** A @p UIView subclass to present 2D content in a @p TGLARView. 31 | * 32 | * Any subviews to this overlay @a must be placed inside @p -contentView. 33 | * 34 | * The overlay draws a callout line from the overlay's @p -targetPosition 35 | * and places the content view at the other end of the callout line. 36 | * 37 | * The content view background is transparent by default and is @a not 38 | * the same as the @p -calloutColor. 39 | */ 40 | @interface TGLARViewOverlay : UIView 41 | 42 | /// The overlay this view belongs to. 43 | @property (nonatomic, weak, nullable) id overlay; 44 | 45 | /// The view containing the all of the overlays content-related views. 46 | @property (nonatomic, readonly, nonnull) UIView *contentView; 47 | 48 | /** If set to @YES place the callout and the @p -contentView below the 49 | * overlay @-targetPosition, above otherwise. Default is @p NO. */ 50 | @property (nonatomic, assign) BOOL upsideDown; 51 | /** If set to @YES place the callout and the @p -contentView to the 52 | * left of @-targetPosition, to the right otherwise. Default is @p NO. */ 53 | @property (nonatomic, assign) BOOL rightAligned; 54 | 55 | /** The vertical distance in points between the overlay @p -targetPosition 56 | * on screen and the top/bottom edge of the @p -contentView. Default is @p 100.0. */ 57 | @property (nonatomic, assign) CGFloat calloutLength; 58 | /// The callout line width in points. Default is 2.0. 59 | @property (nonatomic, assign) CGFloat calloutLineWidth; 60 | /// The callout line color. Default is @p [UIColor whiteColor]. 61 | @property (nonatomic, copy, nullable) UIColor *calloutLineColor; 62 | 63 | /// Private property. For internal use only 64 | @property (nonatomic, assign) GLKVector3 viewPosition; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /TGLAugmentedRealityView/TGLARViewOverlay.m: -------------------------------------------------------------------------------- 1 | // 2 | // TGLARViewOverlay.m 3 | // TGLAugmentedRealityView 4 | // 5 | // Created by Tim Gleue on 13.11.15. 6 | // Copyright (c) 2015 PowerMobile Team (https://powermobile.org) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "TGLARViewOverlay.h" 27 | 28 | @implementation TGLARViewOverlay 29 | 30 | - (instancetype)initWithFrame:(CGRect)frame { 31 | 32 | self = [super initWithFrame:frame]; 33 | 34 | if (self) [self initOverlay]; 35 | 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 40 | 41 | self = [super initWithCoder:aDecoder]; 42 | 43 | if (self) [self initOverlay]; 44 | 45 | return self; 46 | } 47 | 48 | - (void)initOverlay { 49 | 50 | self.backgroundColor = [UIColor clearColor]; 51 | self.opaque = NO; 52 | 53 | self.layer.shadowColor = [UIColor blackColor].CGColor; 54 | self.layer.shadowOffset = CGSizeMake(0.0, 0.0); 55 | self.layer.shadowOpacity = 0.5; 56 | self.layer.shadowRadius = 3.0; 57 | 58 | _contentView = [[UIView alloc] init]; 59 | _contentView.backgroundColor = [UIColor clearColor]; 60 | _contentView.opaque = NO; 61 | _contentView.translatesAutoresizingMaskIntoConstraints = NO; 62 | 63 | [self addSubview:_contentView]; 64 | 65 | _calloutLineWidth = 2.0; 66 | _calloutLineColor = [UIColor whiteColor]; 67 | 68 | _calloutLength = 100.0; 69 | } 70 | 71 | #pragma mark - Accessors 72 | 73 | - (void)setUpsideDown:(BOOL)upsideDown { 74 | 75 | _upsideDown = upsideDown; 76 | 77 | [self setNeedsLayout]; 78 | } 79 | 80 | - (void)setRightAligned:(BOOL)rightAligned { 81 | 82 | _rightAligned = rightAligned; 83 | 84 | [self setNeedsLayout]; 85 | } 86 | 87 | - (void)setCalloutLength:(CGFloat)calloutLength { 88 | 89 | _calloutLength = calloutLength; 90 | 91 | [self setNeedsLayout]; 92 | } 93 | 94 | - (void)setCalloutLineWidth:(CGFloat)calloutLineWidth { 95 | 96 | _calloutLineWidth = calloutLineWidth; 97 | 98 | [self setNeedsDisplay]; 99 | } 100 | 101 | - (void)setCalloutLineColor:(UIColor *)calloutLineColor { 102 | 103 | _calloutLineColor = [calloutLineColor copy]; 104 | 105 | [self setNeedsDisplay]; 106 | } 107 | 108 | #pragma mark - Layout 109 | 110 | - (CGSize)sizeThatFits:(CGSize)size { 111 | 112 | CGSize contentSize = [self.contentView sizeThatFits:size]; 113 | 114 | contentSize.height = MAX(self.calloutLength, contentSize.height); 115 | 116 | return contentSize; 117 | } 118 | 119 | - (void)layoutSubviews { 120 | 121 | [super layoutSubviews]; 122 | 123 | CGRect frame = self.contentView.frame; 124 | 125 | frame.origin.x = 0.0; 126 | frame.origin.y = self.upsideDown ? CGRectGetHeight(self.bounds) - CGRectGetHeight(frame) : 0.0; 127 | 128 | self.contentView.frame = frame; 129 | } 130 | 131 | #pragma mark - Interaction 132 | 133 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 134 | 135 | CGPoint contentPoint = [self.contentView convertPoint:point fromView:self]; 136 | 137 | if ([self.contentView pointInside:contentPoint withEvent:event] && self.isUserInteractionEnabled && !self.isHidden && self.alpha > 0.01) { 138 | 139 | for (UIView *subview in [self.contentView.subviews reverseObjectEnumerator]) { 140 | 141 | CGPoint subviewPoint = [subview convertPoint:point fromView:self]; 142 | UIView *hitView = [subview hitTest:subviewPoint withEvent:event]; 143 | 144 | if (hitView) return hitView; 145 | } 146 | 147 | return self; 148 | } 149 | 150 | return nil; 151 | } 152 | 153 | #pragma mark - Drawing 154 | 155 | - (void)drawRect:(CGRect)rect { 156 | 157 | CGFloat x = self.rightAligned ? CGRectGetWidth(self.bounds) : 0.0; 158 | 159 | CGPoint originPoint = CGPointMake(x, 0.0); 160 | CGPoint targetPoint = CGPointMake(x, CGRectGetHeight(self.bounds)); 161 | 162 | [self.calloutLineColor setStroke]; 163 | 164 | UIBezierPath *calloutLine = [UIBezierPath bezierPath]; 165 | 166 | [calloutLine moveToPoint:originPoint]; 167 | [calloutLine addLineToPoint:targetPoint]; 168 | 169 | calloutLine.lineWidth = self.calloutLineWidth; 170 | [calloutLine stroke]; 171 | } 172 | 173 | @end 174 | --------------------------------------------------------------------------------