Skip to content

Commit 154376e

Browse files
authored
Merge pull request #73 from TaskarCenterAtUW/feature-measure-width
Feature measure width
2 parents 7fa41bd + 884f258 commit 154376e

File tree

6 files changed

+291
-11
lines changed

6 files changed

+291
-11
lines changed

GoInfoGame/GoInfoGame.xcodeproj/project.pbxproj

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@
176176
FA87A81B2B6B042E000A6BEA /* SideWalkWidthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA87A81A2B6B042E000A6BEA /* SideWalkWidthTests.swift */; };
177177
FABF3CF42B7419BB0080EAC9 /* LocationManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FABF3CF32B7419BB0080EAC9 /* LocationManagerDelegate.swift */; };
178178
FABF3CF82B822F120080EAC9 /* CustomSureAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FABF3CF72B822F120080EAC9 /* CustomSureAlert.swift */; };
179+
FABF3CFD2B8607860080EAC9 /* MeasureWidthContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FABF3CFC2B8607860080EAC9 /* MeasureWidthContainer.swift */; };
180+
FABF3D002B8607D80080EAC9 /* MeasureSidewalkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FABF3CFF2B8607D80080EAC9 /* MeasureSidewalkView.swift */; };
179181
FAC9E60F2B04F9C800E2C608 /* OverpassRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9E60E2B04F9C800E2C608 /* OverpassRequestManager.swift */; };
180182
FAC9E6112B06811300E2C608 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9E6102B06811300E2C608 /* LocationManager.swift */; };
181183
FAD5C4F32AFCBE700040C61A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD5C4F22AFCBE700040C61A /* AppDelegate.swift */; };
@@ -441,6 +443,8 @@
441443
FA87A81A2B6B042E000A6BEA /* SideWalkWidthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideWalkWidthTests.swift; sourceTree = "<group>"; };
442444
FABF3CF32B7419BB0080EAC9 /* LocationManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManagerDelegate.swift; sourceTree = "<group>"; };
443445
FABF3CF72B822F120080EAC9 /* CustomSureAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSureAlert.swift; sourceTree = "<group>"; };
446+
FABF3CFC2B8607860080EAC9 /* MeasureWidthContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasureWidthContainer.swift; sourceTree = "<group>"; };
447+
FABF3CFF2B8607D80080EAC9 /* MeasureSidewalkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasureSidewalkView.swift; sourceTree = "<group>"; };
444448
FAC9E60E2B04F9C800E2C608 /* OverpassRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverpassRequestManager.swift; sourceTree = "<group>"; };
445449
FAC9E6102B06811300E2C608 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
446450
FAD5C4EF2AFCBE700040C61A /* GoInfoGame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GoInfoGame.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -587,6 +591,7 @@
587591
05CB71DD2B0FAEC200DED821 /* UI */ = {
588592
isa = PBXGroup;
589593
children = (
594+
FABF3CF92B86062E0080EAC9 /* Camera */,
590595
FA87A80E2B681416000A6BEA /* Utils */,
591596
970D5BD02B62B61300C20BE7 /* MapViewController.swift */,
592597
B0CBEA902B88980A00C430F3 /* Profile */,
@@ -1110,6 +1115,15 @@
11101115
path = Utils;
11111116
sourceTree = "<group>";
11121117
};
1118+
FABF3CF92B86062E0080EAC9 /* Camera */ = {
1119+
isa = PBXGroup;
1120+
children = (
1121+
FABF3CFC2B8607860080EAC9 /* MeasureWidthContainer.swift */,
1122+
FABF3CFF2B8607D80080EAC9 /* MeasureSidewalkView.swift */,
1123+
);
1124+
path = Camera;
1125+
sourceTree = "<group>";
1126+
};
11131127
FAD5C4E62AFCBE700040C61A = {
11141128
isa = PBXGroup;
11151129
children = (
@@ -1678,6 +1692,7 @@
16781692
FABF3CF42B7419BB0080EAC9 /* LocationManagerDelegate.swift in Sources */,
16791693
973FC0382B59363C00878269 /* TactilePavingSteps.swift in Sources */,
16801694
973FC0192B4FB17000878269 /* LocalizedStrings.swift in Sources */,
1695+
FABF3D002B8607D80080EAC9 /* MeasureSidewalkView.swift in Sources */,
16811696
FAC9E6112B06811300E2C608 /* LocationManager.swift in Sources */,
16821697
97439F782B87444200DA43E1 /* CrossingKerbHeightForm.swift in Sources */,
16831698
A4834A3F2B67737500D4F0AA /* StoredChangeset.swift in Sources */,
@@ -1736,6 +1751,7 @@
17361751
B0CCB98E2B8626C600AA73DE /* ProfileViewVM.swift in Sources */,
17371752
0536DD942B0BD59900B04C4B /* OAuthError.swift in Sources */,
17381753
973FC0172B4D567900878269 /* WidthView.swift in Sources */,
1754+
FABF3CFD2B8607860080EAC9 /* MeasureWidthContainer.swift in Sources */,
17391755
97AC1C082B6A45C9004F0BF4 /* SideWalkValidationForm.swift in Sources */,
17401756
FAD5C4F52AFCBE700040C61A /* SceneDelegate.swift in Sources */,
17411757
97AC1C232B726ADC004F0BF4 /* TactilePavingKerbForm.swift in Sources */,
@@ -2230,11 +2246,9 @@
22302246
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
22312247
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
22322248
CODE_SIGN_IDENTITY = "Apple Development";
2233-
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
2234-
CODE_SIGN_STYLE = Manual;
2249+
CODE_SIGN_STYLE = Automatic;
22352250
CURRENT_PROJECT_VERSION = 4;
2236-
DEVELOPMENT_TEAM = "";
2237-
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NPCMG529DV;
2251+
DEVELOPMENT_TEAM = NPCMG529DV;
22382252
GENERATE_INFOPLIST_FILE = YES;
22392253
INFOPLIST_FILE = GoInfoGame/Info.plist;
22402254
INFOPLIST_KEY_NSFileProviderDomainUsageDescription = "";
@@ -2254,7 +2268,6 @@
22542268
PRODUCT_BUNDLE_IDENTIFIER = com.vindago.goinfogame;
22552269
PRODUCT_NAME = "$(TARGET_NAME)";
22562270
PROVISIONING_PROFILE_SPECIFIER = "";
2257-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "GoInfoGame AdHoc";
22582271
SWIFT_EMIT_LOC_STRINGS = YES;
22592272
SWIFT_VERSION = 5.0;
22602273
TARGETED_DEVICE_FAMILY = "1,2";
@@ -2269,11 +2282,9 @@
22692282
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
22702283
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
22712284
CODE_SIGN_IDENTITY = "Apple Development";
2272-
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
2273-
CODE_SIGN_STYLE = Manual;
2285+
CODE_SIGN_STYLE = Automatic;
22742286
CURRENT_PROJECT_VERSION = 4;
2275-
DEVELOPMENT_TEAM = "";
2276-
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NPCMG529DV;
2287+
DEVELOPMENT_TEAM = NPCMG529DV;
22772288
GENERATE_INFOPLIST_FILE = YES;
22782289
INFOPLIST_FILE = GoInfoGame/Info.plist;
22792290
INFOPLIST_KEY_NSFileProviderDomainUsageDescription = "";
@@ -2293,7 +2304,6 @@
22932304
PRODUCT_BUNDLE_IDENTIFIER = com.vindago.goinfogame;
22942305
PRODUCT_NAME = "$(TARGET_NAME)";
22952306
PROVISIONING_PROFILE_SPECIFIER = "";
2296-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "GoInfoGame AdHoc";
22972307
SWIFT_EMIT_LOC_STRINGS = YES;
22982308
SWIFT_VERSION = 5.0;
22992309
TARGETED_DEVICE_FAMILY = "1,2";

GoInfoGame/GoInfoGame/Helpers/Extensions.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import Foundation
99
import MapKit
1010
import RealmSwift
11+
import ARKit
1112

1213

1314
// Extension to check if a polyline intersects with a coordinate
@@ -73,3 +74,45 @@ extension Array where Element: Equatable {
7374

7475
}
7576
}
77+
78+
extension SCNVector3: Equatable {
79+
static func positionFromTransform(_ transform: matrix_float4x4) -> SCNVector3 {
80+
return SCNVector3Make(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z)
81+
}
82+
83+
func distance(from vector: SCNVector3) -> Float {
84+
let distanceX = self.x - vector.x
85+
let distanceY = self.y - vector.y
86+
let distanceZ = self.z - vector.z
87+
88+
return sqrtf( (distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ))
89+
}
90+
91+
public static func ==(lhs: SCNVector3, rhs: SCNVector3) -> Bool {
92+
return (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z)
93+
}
94+
}
95+
96+
extension ARSCNView {
97+
func realWorldVector(screenPos: CGPoint) -> SCNVector3? {
98+
let planeTestResults = self.hitTest(screenPos, types: [.featurePoint])
99+
if let result = planeTestResults.first {
100+
return SCNVector3.positionFromTransform(result.worldTransform)
101+
}
102+
103+
return nil
104+
}
105+
}
106+
107+
extension SCNGeometry {
108+
class func line(from points: [SCNVector3]) -> SCNGeometry {
109+
let sources = SCNGeometrySource(vertices: points)
110+
var indices: [Int32] = []
111+
for i in 0..<points.count {
112+
indices.append(Int32(i))
113+
}
114+
let data = Data(bytes: indices, count: MemoryLayout<Int32>.size * indices.count)
115+
let element = SCNGeometryElement(data: data, primitiveType: .line, primitiveCount: points.count - 1, bytesPerIndex: MemoryLayout<Int32>.size)
116+
return SCNGeometry(sources: [sources], elements: [element])
117+
}
118+
}

GoInfoGame/GoInfoGame/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>NSCameraUsageDescription</key>
6+
<string>This app requires to access your camera to measure width of sidewalk</string>
57
<key>CFBundleURLTypes</key>
68
<array>
79
<dict>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// MeasureSidewalkView.swift
3+
// GoInfoGame
4+
//
5+
// Created by Achyut Kumar M on 21/02/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct MeasureSidewalkView: View {
11+
12+
@State private var aimLabelHidden = true
13+
@State private var notReadyLabelHidden = false
14+
@State private var startMeasuring = false
15+
@State private var resultLabelText = ""
16+
17+
var body: some View {
18+
ZStack {
19+
Spacer()
20+
MeasureWidthContainer(aimLabelHidden: $aimLabelHidden, notReadyLabelHidden: $notReadyLabelHidden, startMeasuring: $startMeasuring, resultLabelText: $resultLabelText)
21+
VStack {
22+
Spacer()
23+
Text("+")
24+
.font(.system(size: 30))
25+
.foregroundStyle(.white)
26+
.padding()
27+
.font(.largeTitle)
28+
29+
Spacer()
30+
Button {
31+
toggleMeasuring()
32+
} label: {
33+
Text("Measure")
34+
.fontWeight(.bold)
35+
.padding()
36+
.foregroundColor(.white)
37+
.background(Color.blue)
38+
.cornerRadius(8)
39+
.overlay(
40+
RoundedRectangle(cornerRadius: 8)
41+
.stroke(Color.blue, lineWidth: 2)
42+
)
43+
}
44+
.padding()
45+
}
46+
}
47+
if !resultLabelText.isEmpty {
48+
VStack {
49+
Text("Width is: \(resultLabelText)")
50+
.padding()
51+
.foregroundColor(.white)
52+
.font(.headline)
53+
}
54+
}
55+
56+
}
57+
58+
func toggleMeasuring() {
59+
startMeasuring.toggle()
60+
}
61+
}
62+
63+
#Preview {
64+
MeasureSidewalkView()
65+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//
2+
// CameraView.swift
3+
// GoInfoGame
4+
//
5+
// Created by Achyut Kumar M on 21/02/24.
6+
//
7+
8+
import SwiftUI
9+
import ARKit
10+
11+
struct MeasureWidthContainer: UIViewRepresentable {
12+
13+
@Binding var aimLabelHidden: Bool
14+
@Binding var notReadyLabelHidden: Bool
15+
@Binding var startMeasuring: Bool
16+
@Binding var resultLabelText: String
17+
18+
let sceneView = ARSCNView()
19+
let session = ARSession()
20+
let sessionConfig = ARWorldTrackingConfiguration()
21+
22+
let vectorZero = SCNVector3()
23+
var startValue = SCNVector3()
24+
var endValue = SCNVector3()
25+
26+
var startPointNode: SCNNode?
27+
var endPointNode: SCNNode?
28+
var lineNode: SCNNode?
29+
30+
@State private var isMeasuring = false
31+
32+
33+
func makeUIView(context: Context) -> ARSCNView {
34+
sceneView.delegate = context.coordinator
35+
sceneView.session = session
36+
session.run(sessionConfig, options: [.resetTracking, .removeExistingAnchors])
37+
38+
return sceneView
39+
}
40+
41+
42+
func updateUIView(_ uiView: ARSCNView, context: Context) {
43+
if startMeasuring {
44+
context.coordinator.startMeasuring()
45+
} else {
46+
if isMeasuring {
47+
context.coordinator.stopMeasuring()
48+
isMeasuring = false
49+
}
50+
}
51+
}
52+
53+
func makeCoordinator() -> Coordinator {
54+
Coordinator(self)
55+
}
56+
57+
58+
class Coordinator: NSObject, ARSCNViewDelegate {
59+
var parent: MeasureWidthContainer
60+
61+
init(_ parent: MeasureWidthContainer) {
62+
self.parent = parent
63+
}
64+
65+
@objc func measuringChanged() {
66+
if parent.startMeasuring {
67+
startMeasuring()
68+
} else {
69+
stopMeasuring()
70+
}
71+
}
72+
73+
func startMeasuring() {
74+
parent.resetValues()
75+
parent.startMeasuring = true
76+
77+
}
78+
79+
func stopMeasuring() {
80+
parent.startMeasuring = false
81+
parent.endPointNode = parent.createPointNode(at: parent.endValue, color: .white)
82+
parent.sceneView.scene.rootNode.addChildNode(parent.endPointNode!)
83+
parent.updateResultLabel(parent.startValue.distance(from: parent.endValue))
84+
}
85+
86+
87+
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
88+
DispatchQueue.main.async {
89+
self.parent.detectObjects()
90+
}
91+
}
92+
}
93+
94+
mutating func detectObjects() {
95+
if let worldPos = sceneView.realWorldVector(screenPos: sceneView.center) {
96+
aimLabelHidden = false
97+
notReadyLabelHidden = true
98+
if startMeasuring {
99+
if startValue == vectorZero {
100+
startValue = worldPos
101+
startPointNode = createPointNode(at: worldPos, color: .white)
102+
sceneView.scene.rootNode.addChildNode(startPointNode!)
103+
isMeasuring = true
104+
}
105+
106+
endValue = worldPos
107+
108+
if let line = lineNode {
109+
line.removeFromParentNode()
110+
}
111+
lineNode = createLineNode(from: startValue, to: endValue)
112+
sceneView.scene.rootNode.addChildNode(lineNode!)
113+
}
114+
}
115+
}
116+
117+
mutating func resetValues() {
118+
startMeasuring = false
119+
startValue = SCNVector3()
120+
endValue = SCNVector3()
121+
122+
startPointNode?.removeFromParentNode()
123+
endPointNode?.removeFromParentNode()
124+
lineNode?.removeFromParentNode()
125+
126+
updateResultLabel(0.0)
127+
128+
}
129+
130+
func updateResultLabel(_ value: Float) {
131+
let cm = value * 100.0
132+
let inch = cm*0.3937007874
133+
DispatchQueue.main.async {
134+
self.resultLabelText = String(format: "%.2f cm / %.2f\"", cm, inch)
135+
}
136+
}
137+
138+
func createPointNode(at position: SCNVector3, color: UIColor) -> SCNNode {
139+
let sphere = SCNSphere(radius: 0.005)
140+
sphere.firstMaterial?.diffuse.contents = color
141+
let node = SCNNode(geometry: sphere)
142+
node.position = position
143+
return node
144+
}
145+
146+
func createLineNode(from startPoint: SCNVector3, to endPoint: SCNVector3) -> SCNNode {
147+
let vertices: [SCNVector3] = [startPoint, endPoint]
148+
let line = SCNGeometry.line(from: vertices)
149+
let lineNode = SCNNode(geometry: line)
150+
return lineNode
151+
}
152+
}

0 commit comments

Comments
 (0)