replate-camera 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,7 +45,6 @@ extension UIImage {
45
45
  class ReplateCameraView : UIView, ARSessionDelegate {
46
46
 
47
47
  static var arView: ARView!
48
- static var anchor: ARAnchor!
49
48
  static var anchorEntity: AnchorEntity!
50
49
  static var model: Entity!
51
50
  static var spheresModels: [ModelEntity] = []
@@ -53,20 +52,27 @@ class ReplateCameraView : UIView, ARSessionDelegate {
53
52
  static var lowerSpheresSet: [Bool] = [Bool](repeating: false, count: 72)
54
53
  static var totalPhotosTaken: Int = 0
55
54
  static var photosFromDifferentAnglesTaken = 0
55
+ static var INSTANCE: ReplateCameraView!
56
56
 
57
57
  override init(frame: CGRect) {
58
58
  super.init(frame: frame)
59
59
  requestCameraPermissions()
60
60
  // setupAR()
61
+ ReplateCameraView.INSTANCE = self
61
62
  }
62
63
 
63
64
  required init?(coder: NSCoder) {
64
65
  super.init(coder: coder)
65
66
  requestCameraPermissions()
67
+ ReplateCameraView.INSTANCE = self
66
68
  // setupAR()
67
69
  }
68
70
 
69
-
71
+ static func addRecognizer(){
72
+ let recognizer = UITapGestureRecognizer(target: ReplateCameraView.INSTANCE,
73
+ action: #selector(ReplateCameraView.INSTANCE.viewTapped(_:)))
74
+ ReplateCameraView.arView.addGestureRecognizer(recognizer)
75
+ }
70
76
 
71
77
  func requestCameraPermissions(){
72
78
 
@@ -94,6 +100,65 @@ class ReplateCameraView : UIView, ARSessionDelegate {
94
100
  print("Width: \(width), Height: \(height)")
95
101
  self.setupAR()
96
102
  }
103
+
104
+
105
+ @objc private func viewTapped(_ recognizer: UITapGestureRecognizer) {
106
+ print("VIEW TAPPED")
107
+ // guard !ReplateCameraView.arView.canBecomeFocused else {
108
+ // return
109
+ // }
110
+
111
+ let tapLocation: CGPoint = recognizer.location(in: ReplateCameraView.arView)
112
+ let estimatedPlane: ARRaycastQuery.Target = .estimatedPlane
113
+ let alignment: ARRaycastQuery.TargetAlignment = .horizontal
114
+
115
+ let result: [ARRaycastResult] = ReplateCameraView.arView.raycast(from: tapLocation,
116
+ allowing: estimatedPlane,
117
+ alignment: alignment)
118
+
119
+ guard let rayCast: ARRaycastResult = result.first
120
+ else { return }
121
+ let anchor = AnchorEntity(raycastResult: rayCast)
122
+ print("ANCHOR FOUND\n", anchor.transform)
123
+ if (ReplateCameraView.model == nil && ReplateCameraView.anchorEntity == nil){
124
+ ReplateCameraView.anchorEntity = anchor
125
+ let anchorTransform = anchor.transform
126
+ // let path = Bundle.main.path(forResource: "anchor", ofType: "usdz")!
127
+ // let url = URL(fileURLWithPath: path)
128
+ // let entity: ModelEntity = try! ModelEntity.loadModel(contentsOf: url)
129
+
130
+ // if #available(iOS 15.0, *) {
131
+ // entity.model!.mesh.
132
+ // ReplateCameraView.spheresModels = Array(entity.model!.mesh.contents.models)
133
+ // }
134
+ // entity.scale *= 4.5
135
+ // entity.position = SIMD3(anchorTransform.columns.3.x, anchorTransform.columns.3.y, anchorTransform.columns.3.z)
136
+
137
+ func createSphere(position: SIMD3<Float>) -> ModelEntity {
138
+ let sphereMesh = MeshResource.generateSphere(radius: 0.0025)
139
+ let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), isMetallic: false)])
140
+ sphereEntity.position = position
141
+ return sphereEntity
142
+ }
143
+
144
+ func createSpheres(y: Float){
145
+ let radius = Float(0.1)
146
+ for i in 0..<72 {
147
+ let angle = Float(i) * (Float.pi / 180) * 5 // 10 degrees in radians
148
+ let x = radius * cos(angle)
149
+ let z = radius * sin(angle)
150
+ let spherePosition = SIMD3<Float>(x, y, z)
151
+ let sphereEntity = createSphere(position: spherePosition)
152
+ ReplateCameraView.spheresModels.append(sphereEntity)
153
+ ReplateCameraView.anchorEntity.addChild(sphereEntity)
154
+ }
155
+ }
156
+
157
+ createSpheres(y: 0.0)
158
+ createSpheres(y: 0.3)
159
+ ReplateCameraView.arView.scene.anchors.append(ReplateCameraView.anchorEntity)
160
+ }
161
+ }
97
162
 
98
163
  func setupAR() {
99
164
  print("Setup AR")
@@ -109,6 +174,10 @@ class ReplateCameraView : UIView, ARSessionDelegate {
109
174
  // else { fatalError("See no reference object") }
110
175
  // print(obj)
111
176
  configuration.planeDetection = ARWorldTrackingConfiguration.PlaneDetection.horizontal
177
+ // ReplateCameraView.arView.debugOptions = [
178
+ // .showAnchorOrigins,
179
+ // .showAnchorGeometry
180
+ // ]
112
181
  if #available(iOS 16.0, *) {
113
182
  print("recommendedVideoFormatForHighResolutionFrameCapturing")
114
183
  configuration.videoFormat = ARWorldTrackingConfiguration.recommendedVideoFormatForHighResolutionFrameCapturing ?? ARWorldTrackingConfiguration.recommendedVideoFormatFor4KResolution ?? ARWorldTrackingConfiguration.supportedVideoFormats[0]
@@ -123,61 +192,14 @@ class ReplateCameraView : UIView, ARSessionDelegate {
123
192
  }
124
193
  // configuration.detectionObjects = obj
125
194
  ReplateCameraView.arView.session.run(configuration)
195
+
196
+ ReplateCameraView.arView.addCoaching()
126
197
  }
127
198
 
128
199
  func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
129
- print("ANCHOR FOUND")
130
- if (ReplateCameraView.anchor == nil){
131
- guard let _anchor = anchors.first else { return }
132
- ReplateCameraView.anchor = _anchor
133
- }
134
- if (ReplateCameraView.model == nil && ReplateCameraView.anchorEntity == nil){
135
- let anchorTransform = ReplateCameraView.anchor.transform
136
- // let path = Bundle.main.path(forResource: "anchor", ofType: "usdz")!
137
- // let url = URL(fileURLWithPath: path)
138
- // let entity: ModelEntity = try! ModelEntity.loadModel(contentsOf: url)
139
-
140
- // if #available(iOS 15.0, *) {
141
- // entity.model!.mesh.
142
- // ReplateCameraView.spheresModels = Array(entity.model!.mesh.contents.models)
143
- // }
144
- // entity.scale *= 4.5
145
- // entity.position = SIMD3(anchorTransform.columns.3.x, anchorTransform.columns.3.y, anchorTransform.columns.3.z)
146
-
147
- func createSphere(position: SIMD3<Float>) -> ModelEntity {
148
- let sphereMesh = MeshResource.generateSphere(radius: 0.0025)
149
- let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), isMetallic: false)])
150
- sphereEntity.position = position
151
- return sphereEntity
152
- }
153
-
154
- func createSpheres(y: Float){
155
- let radius = Float(0.1)
156
- for i in 0..<72 {
157
- let angle = Float(i) * (Float.pi / 180) * 5 // 10 degrees in radians
158
- let x = radius * cos(angle)
159
- let z = radius * sin(angle)
160
- let spherePosition = SIMD3<Float>(x, y, z)
161
- let sphereEntity = createSphere(position: spherePosition)
162
- ReplateCameraView.spheresModels.append(sphereEntity)
163
- ReplateCameraView.anchorEntity.addChild(sphereEntity)
164
- }
165
- }
166
-
167
- ReplateCameraView.anchorEntity = AnchorEntity()
168
- createSpheres(y: 0.0)
169
- createSpheres(y: 0.3)
170
- ReplateCameraView.arView.scene.anchors.append(ReplateCameraView.anchorEntity)
171
- }
200
+
172
201
  }
173
202
 
174
- // @objc func setCameraRect(_ node: NSNumber, rect: NSDictionary) {
175
- // let x = rect["x"] as? CGFloat ?? 0
176
- // let y = rect["y"] as? CGFloat ?? 0
177
- // let width = rect["width"] as? CGFloat ?? 0
178
- // let height = rect["height"] as? CGFloat ?? 0
179
- //// arView.(CGRect(x: x, y: y, width: width, height: height))
180
- // }
181
203
 
182
204
  @objc var color: String = "" {
183
205
  didSet {
@@ -220,11 +242,17 @@ class ReplateCameraView : UIView, ARSessionDelegate {
220
242
  ReplateCameraView.arView.session.run(ARWorldTrackingConfiguration())
221
243
  }
222
244
 
223
-
224
245
  }
225
246
 
226
247
  @objc(ReplateCameraController)
227
- class ReplateCameraController: NSObject {
248
+ class ReplateCameraController: RCTEventEmitter {
249
+
250
+ static var INSTANCE: ReplateCameraController!
251
+
252
+ override init() {
253
+ super.init()
254
+ ReplateCameraController.INSTANCE = self
255
+ }
228
256
 
229
257
  @objc(getPhotosCount:rejecter:)
230
258
  func getPhotosCount(_ resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) -> Void{
@@ -246,17 +274,17 @@ class ReplateCameraController: NSObject {
246
274
 
247
275
  //DEVICE ORIENTATION
248
276
  guard let anchorNode = ReplateCameraView.anchorEntity else {
249
- rejecter("[ReplateCameraController]", "Error saving photo", NSError(domain: "ReplateCameraController", code: 001, userInfo: nil));
277
+ rejecter("[ReplateCameraController]", "No anchor set yet", NSError(domain: "ReplateCameraController", code: 001, userInfo: nil));
250
278
  return
251
279
  }
252
280
 
253
281
  // Assuming you have two points
254
- let point1 = SIMD3<Float>(anchorNode.position.x,
255
- anchorNode.position.y,
256
- anchorNode.position.z)
257
- let point2 = SIMD3<Float>(anchorNode.position.x,
258
- anchorNode.position.y + 0.3,
259
- anchorNode.position.z)
282
+ let point1 = SIMD3<Float>(anchorNode.position(relativeTo: nil).x,
283
+ anchorNode.position(relativeTo: nil).y,
284
+ anchorNode.position(relativeTo: nil).z)
285
+ let point2 = SIMD3<Float>(anchorNode.position(relativeTo: nil).x,
286
+ anchorNode.position(relativeTo: nil).y + 0.3,
287
+ anchorNode.position(relativeTo: nil).z)
260
288
 
261
289
  // Function to calculate the angle between two vectors
262
290
  func angleBetweenVectors(_ vector1: SIMD3<Float>, _ vector2: SIMD3<Float>) -> Float {
@@ -265,22 +293,33 @@ class ReplateCameraController: NSObject {
265
293
  }
266
294
 
267
295
  // Threshold angle for considering if the device is pointing towards a point
268
- let thresholdAngle: Float = 0.3 // Adjust this threshold as needed
269
296
  var deviceTargetInFocus = -1
270
297
  // Check if the device is pointing towards one of the two points
271
298
  if let cameraTransform = ReplateCameraView.arView.session.currentFrame?.camera.transform {
272
299
  let deviceDirection = SIMD3<Float>(-cameraTransform.columns.2.x, -cameraTransform.columns.2.y, -cameraTransform.columns.2.z)
273
300
 
274
301
  let cameraPosition = SIMD3<Float>(cameraTransform.columns.3.x, cameraTransform.columns.3.y, cameraTransform.columns.3.z)
275
-
276
302
  let directionToFirstPoint = normalize(point1 - cameraPosition)
277
303
  let directionToSecondPoint = normalize(point2 - cameraPosition)
278
304
 
305
+ let point1Distance = distance(point1, cameraPosition)
306
+ let point2Distance = distance(point2, cameraPosition)
307
+ let averageDistance = (point1Distance + point2Distance) / 2.0
308
+
309
+ let baseThreshold: Float = 0.6 // adjust as needed
310
+ let distanceFactor: Float = 0.2 // adjust as needed
311
+
312
+ let dynamicThreshold = baseThreshold + distanceFactor * sqrt(averageDistance)
313
+
314
+
279
315
  let angleToFirstPoint = angleBetweenVectors(deviceDirection, directionToFirstPoint)
280
316
  let angleToSecondPoint = angleBetweenVectors(deviceDirection, directionToSecondPoint)
281
- print("Camera Y: \(cameraPosition.y)")
282
- let isPointingAtFirstPoint = angleToFirstPoint < thresholdAngle && cameraPosition.y < 0.25
283
- let isPointingAtSecondPoint = angleToSecondPoint < thresholdAngle && cameraPosition.y >= 0.25
317
+ print("Camera: \(cameraPosition)")
318
+ print("Point 1 position: \(point1) Point 2 position: \(point2)")
319
+ print("Angle to first: ", angleToFirstPoint, " Angle to second: ", angleToSecondPoint)
320
+ print("Threshold \(dynamicThreshold)")
321
+ let isPointingAtFirstPoint = angleToFirstPoint < dynamicThreshold && cameraPosition.y < anchorNode.position.y + 0.20
322
+ let isPointingAtSecondPoint = angleToSecondPoint < dynamicThreshold && cameraPosition.y >= anchorNode.position.y + 0.20
284
323
  if (isPointingAtFirstPoint) {
285
324
  deviceTargetInFocus = 0
286
325
  }else if(isPointingAtSecondPoint){
@@ -304,6 +343,18 @@ class ReplateCameraController: NSObject {
304
343
  let uiImage = UIImage(cgImage: cgImage)
305
344
  let finImage = uiImage.rotate(radians: .pi/2) // Adjust radians as needed
306
345
 
346
+ guard let components = finImage.averageColor()?.getRGBComponents()
347
+ else {
348
+ rejecter("[ReplateCameraController]", "Cannot get color components", NSError(domain: "ReplateCameraController", code: 003, userInfo: nil))
349
+ return
350
+ }
351
+ let averagePixelColor = components.red + components.blue + components.green / 3
352
+ print("Average pixel color: \(averagePixelColor)")
353
+ if ((components.red + components.blue + components.green) / 3 < 0.15){
354
+ rejecter("[ReplateCameraController]", "Image too dark", NSError(domain: "ReplateCameraController", code: 004, userInfo: nil))
355
+ return
356
+ }
357
+
307
358
  print("Saving photo")
308
359
  if let url = ReplateCameraController.saveImageAsJPEG(finImage) {
309
360
  resolver(url.absoluteString)
@@ -330,7 +381,6 @@ class ReplateCameraController: NSObject {
330
381
  }
331
382
 
332
383
 
333
-
334
384
  static func saveImageAsJPEG(_ image: UIImage) -> URL? {
335
385
  // Convert UIImage to Data with JPEG representation
336
386
  guard let imageData = image.jpegData(compressionQuality: 1.0) else {
@@ -363,12 +413,17 @@ class ReplateCameraController: NSObject {
363
413
  return nil
364
414
  }
365
415
  }
366
-
367
-
368
- @objc
369
- func constantsToExport() -> [String: Any]! {
370
- return ["someKey": "someValue"]
371
- }
416
+
417
+ @objc
418
+ func sendTutorialEndedEvent(){
419
+ self.sendEvent(withName: "arTutorialCompleted", body: true)
420
+ }
421
+
422
+ @objc
423
+ override func supportedEvents() -> [String]! {
424
+ return ["arTutorialCompleted"]
425
+ }
426
+
372
427
 
373
428
  func updateSpheres(deviceTargetInFocus: Int) {
374
429
  guard let anchorNode = ReplateCameraView.anchorEntity else { return }
@@ -378,9 +433,9 @@ class ReplateCameraController: NSObject {
378
433
  let cameraTransform = frame.camera.transform
379
434
 
380
435
  // Calculate the angle between the camera and the anchor
381
- let anchorPosition = SCNVector3(anchorNode.position.x,
382
- anchorNode.position.y,
383
- anchorNode.position.z)
436
+ let anchorPosition = SCNVector3(anchorNode.position(relativeTo: nil).x,
437
+ anchorNode.position(relativeTo: nil).y,
438
+ anchorNode.position(relativeTo: nil).z)
384
439
  let cameraPosition = SCNVector3(cameraTransform.columns.3.x,
385
440
  cameraTransform.columns.3.y,
386
441
  cameraTransform.columns.3.z)
@@ -409,9 +464,9 @@ class ReplateCameraController: NSObject {
409
464
  }
410
465
  }
411
466
 
412
- func calculateAngle(_ vector1: SCNVector3, _ vector2: SCNVector3) -> Float {
467
+ func calculateAngle(_ camera: SCNVector3, _ anchor: SCNVector3) -> Float {
413
468
  // Calculate the angle in 2D plane (x-z plane) using atan2
414
- let angle = atan2(vector1.z, vector1.x)
469
+ let angle = atan2(camera.z - anchor.z, camera.x - anchor.x)
415
470
 
416
471
  // Convert from radians to degrees
417
472
  var angleInDegrees = GLKMathRadiansToDegrees(Float(angle))
@@ -421,6 +476,14 @@ class ReplateCameraController: NSObject {
421
476
  angleInDegrees += 360
422
477
  }
423
478
 
479
+ // Rotate by 2.5 degrees to consider sphere-center, not sphere-begin
480
+ angleInDegrees += 2.5
481
+
482
+ // If rotating resulted in angle > 360 degrees, subtract 360
483
+ if angleInDegrees >= 360 {
484
+ angleInDegrees -= 360
485
+ }
486
+
424
487
  return angleInDegrees
425
488
  }
426
489
 
@@ -436,3 +499,94 @@ extension SCNVector3 {
436
499
  return a.x*b.x + a.y*b.y + a.z*b.z
437
500
  }
438
501
  }
502
+
503
+ extension ARView: ARCoachingOverlayViewDelegate {
504
+ func addCoaching() {
505
+ print("ADD COACHING")
506
+ // Create a ARCoachingOverlayView object
507
+ let coachingOverlay = ARCoachingOverlayView()
508
+ // Make sure it rescales if the device orientation changes
509
+ coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
510
+ self.addSubview(coachingOverlay)
511
+ coachingOverlay.center = self.convert(self.center, from:self.superview)
512
+ // Set the Augmented Reality goal
513
+ coachingOverlay.goal = .horizontalPlane
514
+ // Set the ARSession
515
+ coachingOverlay.session = self.session
516
+ // Set the delegate for any callbacks
517
+ coachingOverlay.delegate = self
518
+ coachingOverlay.setActive(true, animated: true)
519
+ }
520
+ // Example callback for the delegate object
521
+ public func coachingOverlayViewDidDeactivate(
522
+ _ coachingOverlayView: ARCoachingOverlayView
523
+ ) {
524
+ print("DEACTIVATED")
525
+ ReplateCameraController.INSTANCE.sendTutorialEndedEvent()
526
+ ReplateCameraView.addRecognizer()
527
+ print("CRASHED")
528
+ }
529
+ }
530
+
531
+ extension UIImage {
532
+ func averageColor() -> UIColor? {
533
+ // Convert UIImage to CGImage
534
+ guard let cgImage = self.cgImage else { return nil }
535
+
536
+ // Get width and height of the image
537
+ let width = cgImage.width
538
+ let height = cgImage.height
539
+
540
+ // Create a data provider from CGImage
541
+ guard let dataProvider = cgImage.dataProvider else { return nil }
542
+
543
+ // Access pixel data
544
+ guard let pixelData = dataProvider.data else { return nil }
545
+
546
+ // Create a pointer to the pixel data
547
+ let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
548
+
549
+ var totalRed: CGFloat = 0
550
+ var totalGreen: CGFloat = 0
551
+ var totalBlue: CGFloat = 0
552
+
553
+ // Loop through each pixel and calculate sum of RGB values
554
+ for y in 0..<height {
555
+ for x in 0..<width {
556
+ let pixelInfo: Int = ((width * y) + x) * 4
557
+ let red = CGFloat(data[pixelInfo]) / 255.0
558
+ let green = CGFloat(data[pixelInfo + 1]) / 255.0
559
+ let blue = CGFloat(data[pixelInfo + 2]) / 255.0
560
+
561
+ totalRed += red
562
+ totalGreen += green
563
+ totalBlue += blue
564
+ }
565
+ }
566
+
567
+ // Calculate average RGB values
568
+ let count = CGFloat(width * height)
569
+ let averageRed = totalRed / count
570
+ let averageGreen = totalGreen / count
571
+ let averageBlue = totalBlue / count
572
+
573
+ // Create and return average color
574
+ return UIColor(red: averageRed, green: averageGreen, blue: averageBlue, alpha: 1.0)
575
+ }
576
+ }
577
+
578
+ extension UIColor {
579
+ func getRGBComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat)? {
580
+ var red: CGFloat = 0
581
+ var green: CGFloat = 0
582
+ var blue: CGFloat = 0
583
+ var alpha: CGFloat = 0
584
+
585
+ // Check if the color can be converted to RGB
586
+ guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
587
+ return nil
588
+ }
589
+
590
+ return (red, green, blue)
591
+ }
592
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replate-camera",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Camera component for Replate Manager",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",