replate-camera 0.1.39 → 0.1.41

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.
@@ -140,37 +140,62 @@ class ReplateCameraView: UIView, ARSessionDelegate {
140
140
 
141
141
  switch gestureRecognizer.state {
142
142
  case .changed:
143
- // Calculate the scale based on the gesture recognizer's scale
144
- let scale = Float(gestureRecognizer.scale)
145
-
146
- // Apply the scale to the anchor entity's transform
147
- let transform = ReplateCameraView.anchorEntity.transform
148
-
149
- ReplateCameraView.spheresModels.forEach { entity in
150
- ReplateCameraView.anchorEntity.removeChild(entity)
151
- }
152
- ReplateCameraView.anchorEntity.removeChild(ReplateCameraView.focusModel)
153
- ReplateCameraView.spheresModels = []
154
- ReplateCameraView.sphereRadius *= scale
155
- ReplateCameraView.spheresRadius *= scale
156
- ReplateCameraView.sphereAngle *= scale
157
- createSpheres(y: ReplateCameraView.spheresHeight)
158
- createSpheres(y: ReplateCameraView.distanceBetweenCircles + ReplateCameraView.spheresHeight)
159
- createFocusSphere()
160
- for i in 0...71 {
161
- let material = SimpleMaterial(color: .green, roughness: 1, isMetallic: false)
162
- if (ReplateCameraView.upperSpheresSet[i]) {
163
- let entity = ReplateCameraView.spheresModels[72 + i]
164
- entity.model?.materials[0] = material
143
+ // Ensure execution on the main thread
144
+ DispatchQueue.main.async {
145
+ // Calculate the scale based on the gesture recognizer's scale
146
+ let scale = Float(gestureRecognizer.scale)
147
+
148
+ // Ensure anchor entity is not nil before proceeding
149
+ guard let anchorEntity = ReplateCameraView.anchorEntity else {
150
+ print("[handlePinch] Anchor entity is nil.")
151
+ return
165
152
  }
166
- if (ReplateCameraView.lowerSpheresSet[i]) {
167
- let entity = ReplateCameraView.spheresModels[i]
168
- entity.model?.materials[0] = material
153
+
154
+ // Remove all child entities safely
155
+ ReplateCameraView.spheresModels.forEach { entity in
156
+ anchorEntity.removeChild(entity)
157
+ }
158
+ if let focusModel = ReplateCameraView.focusModel {
159
+ anchorEntity.removeChild(focusModel)
160
+ }
161
+
162
+ // Clear spheres models array
163
+ ReplateCameraView.spheresModels = []
164
+
165
+ // Update the scales
166
+ ReplateCameraView.sphereRadius *= scale
167
+ ReplateCameraView.spheresRadius *= scale
168
+ ReplateCameraView.sphereAngle *= scale
169
+
170
+ // Recreate spheres and the focus sphere
171
+ self.createSpheres(y: ReplateCameraView.spheresHeight)
172
+ self.createSpheres(y: ReplateCameraView.distanceBetweenCircles + ReplateCameraView.spheresHeight)
173
+ self.createFocusSphere()
174
+
175
+ // Update the material of the spheres based on their state
176
+ for i in 0..<72 {
177
+ let material = SimpleMaterial(color: .green, roughness: 1, isMetallic: false)
178
+ if ReplateCameraView.upperSpheresSet[i] {
179
+ if 72 + i < ReplateCameraView.spheresModels.count {
180
+ let entity = ReplateCameraView.spheresModels[72 + i]
181
+ entity.model?.materials[0] = material
182
+ } else {
183
+ print("[handlePinch] Upper sphere index out of bounds: \(72 + i)")
184
+ }
185
+ }
186
+ if ReplateCameraView.lowerSpheresSet[i] {
187
+ if i < ReplateCameraView.spheresModels.count {
188
+ let entity = ReplateCameraView.spheresModels[i]
189
+ entity.model?.materials[0] = material
190
+ } else {
191
+ print("[handlePinch] Lower sphere index out of bounds: \(i)")
192
+ }
193
+ }
169
194
  }
170
195
 
196
+ // Reset the gesture recognizer's scale to 1 to avoid cumulative scaling
197
+ gestureRecognizer.scale = 1.0
171
198
  }
172
- // Reset the gesture recognizer's scale to 1 to avoid cumulative scaling
173
- gestureRecognizer.scale = 1.0
174
199
  default:
175
200
  break
176
201
  }
@@ -247,18 +272,45 @@ class ReplateCameraView: UIView, ARSessionDelegate {
247
272
  }
248
273
 
249
274
  func createFocusSphere() {
250
- let sphereMesh = MeshResource.generateSphere(radius: ReplateCameraView.sphereRadius * 1.5)
251
- let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), roughness: 1, isMetallic: false)])
252
- sphereEntity.position = SIMD3(x: 0, y: ReplateCameraView.spheresHeight + (ReplateCameraView.distanceBetweenCircles / 2), z: 0)
253
- sphereEntity.model?.materials = [SimpleMaterial(color: .green.withAlphaComponent(1), roughness: 1, isMetallic: false)]
254
- ReplateCameraView.focusModel = sphereEntity
255
- ReplateCameraView.anchorEntity.addChild(sphereEntity)
275
+ DispatchQueue.main.async {
276
+ // Generate the sphere mesh
277
+ let sphereMesh = MeshResource.generateSphere(radius: ReplateCameraView.sphereRadius * 1.5)
278
+
279
+ // Create the sphere entity with initial material
280
+ let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(0.7), roughness: 1, isMetallic: false)])
281
+
282
+ // Set the position for the sphere entity
283
+ sphereEntity.position = SIMD3(x: 0, y: ReplateCameraView.spheresHeight + (ReplateCameraView.distanceBetweenCircles / 2), z: 0)
284
+
285
+ // Update the material of the sphere entity
286
+ sphereEntity.model?.materials = [SimpleMaterial(color: .green.withAlphaComponent(1), roughness: 1, isMetallic: false)]
287
+
288
+ // Set the focus model for the global state
289
+ ReplateCameraView.focusModel = sphereEntity
290
+
291
+ // Safely add the sphere entity to the anchor entity
292
+ ReplateCameraView.anchorEntity?.addChild(sphereEntity)
293
+ }
256
294
  }
257
295
 
258
296
  func createSphere(position: SIMD3<Float>) -> ModelEntity {
297
+ // Ensure execution on the main thread
298
+ guard Thread.isMainThread else {
299
+ return DispatchQueue.main.sync {
300
+ return createSphere(position: position)
301
+ }
302
+ }
303
+
304
+ // Generate sphere mesh safely
259
305
  let sphereMesh = MeshResource.generateSphere(radius: ReplateCameraView.sphereRadius)
306
+
307
+ // Create sphere entity with the specified material
260
308
  let sphereEntity = ModelEntity(mesh: sphereMesh, materials: [SimpleMaterial(color: .white.withAlphaComponent(1), roughness: 1, isMetallic: false)])
309
+
310
+ // Set the position for the sphere entity
261
311
  sphereEntity.position = position
312
+
313
+ // Return the created sphere entity
262
314
  return sphereEntity
263
315
  }
264
316
 
@@ -619,64 +671,97 @@ class ReplateCameraController: NSObject {
619
671
  }
620
672
 
621
673
  func updateSpheres(deviceTargetInFocus: Int) -> Bool {
622
- // When the user pinch the screen the sheres are created new, we have to make sure that all the spheres have
623
- // been recreated before running the updateSpheres logic
624
- if(ReplateCameraView.spheresModels.count < 144){
625
- return false
626
- }
627
-
628
- guard let anchorNode = ReplateCameraView.anchorEntity else {
629
- return false
630
- }
631
-
632
- // Get the camera's pose
633
- guard let frame = ReplateCameraView.arView.session.currentFrame else {
634
- return false
635
- }
636
-
637
- let cameraTransform = frame.camera.transform
638
-
639
- // Calculate the angle between the camera and the anchor
640
- let angleDegrees = ReplateCameraController.angleBetweenAnchorXAndCamera(anchor: anchorNode, cameraTransform: cameraTransform)
641
- let sphereIndex = max(Int(round(angleDegrees / 5.0)), 0) % 72 // Ensure sphereIndex stays within 0-71 bounds
642
-
643
- var mesh: ModelEntity?
644
- var newAngle = false
645
- var callback: RCTResponseSenderBlock? = nil
646
- print("Sphere index \(sphereIndex) - Spheres length \(ReplateCameraView.spheresModels.count)")
647
- if deviceTargetInFocus == 1 {
648
- if !ReplateCameraView.upperSpheresSet[sphereIndex] {
649
- ReplateCameraView.upperSpheresSet[sphereIndex] = true
650
- ReplateCameraView.photosFromDifferentAnglesTaken += 1
651
- newAngle = true
652
- mesh = ReplateCameraView.spheresModels[72 + sphereIndex]
653
- if ReplateCameraView.upperSpheresSet.allSatisfy({ $0 }) {
654
- callback = ReplateCameraController.completedUpperSpheresCallback
655
- ReplateCameraController.completedUpperSpheresCallback = nil
656
- }
674
+ // Ensure we're on the main thread
675
+ DispatchQueue.main.sync {
676
+ // When the user pinches the screen, spheres are recreated,
677
+ // we have to make sure all spheres have been recreated before proceeding
678
+ if (ReplateCameraView.spheresModels.count < 144) {
679
+ print("[updateSpheres] Spheres not fully initialized. Count: \(ReplateCameraView.spheresModels.count)")
680
+ return false
657
681
  }
658
- } else if deviceTargetInFocus == 0 {
659
- if !ReplateCameraView.lowerSpheresSet[sphereIndex] {
660
- ReplateCameraView.lowerSpheresSet[sphereIndex] = true
661
- ReplateCameraView.photosFromDifferentAnglesTaken += 1
662
- newAngle = true
663
- mesh = ReplateCameraView.spheresModels[sphereIndex]
664
- if ReplateCameraView.lowerSpheresSet.allSatisfy({ $0 }) {
665
- callback = ReplateCameraController.completedLowerSpheresCallback
666
- ReplateCameraController.completedLowerSpheresCallback = nil
682
+
683
+ guard let anchorNode = ReplateCameraView.anchorEntity else {
684
+ print("[updateSpheres] No anchor entity found.")
685
+ return false
686
+ }
687
+
688
+ // Get the camera's pose
689
+ guard let frame = ReplateCameraView.arView.session.currentFrame else {
690
+ print("[updateSpheres] No current frame available.")
691
+ return false
692
+ }
693
+
694
+ let cameraTransform = frame.camera.transform
695
+
696
+ // Calculate the angle between the camera and the anchor
697
+ let angleDegrees = ReplateCameraController.angleBetweenAnchorXAndCamera(anchor: anchorNode, cameraTransform: cameraTransform)
698
+ let sphereIndex = max(Int(round(angleDegrees / 5.0)), 0) % 72 // Ensure sphereIndex stays within 0-71 bounds
699
+
700
+ var mesh: ModelEntity?
701
+ var newAngle = false
702
+ var callback: RCTResponseSenderBlock? = nil
703
+ print("Sphere index \(sphereIndex) - Spheres length \(ReplateCameraView.spheresModels.count)")
704
+
705
+ if deviceTargetInFocus == 1 {
706
+ if sphereIndex >= ReplateCameraView.upperSpheresSet.count {
707
+ print("[updateSpheres] Sphere index out of range. Index: \(sphereIndex), Count: \(ReplateCameraView.upperSpheresSet.count)")
708
+ return false
709
+ }
710
+
711
+ if !ReplateCameraView.upperSpheresSet[sphereIndex] {
712
+ ReplateCameraView.upperSpheresSet[sphereIndex] = true
713
+ ReplateCameraView.photosFromDifferentAnglesTaken += 1
714
+ newAngle = true
715
+
716
+ if 72 + sphereIndex >= ReplateCameraView.spheresModels.count {
717
+ print("[updateSpheres] Upper spheresModels index out of range. Index: \(72 + sphereIndex), Count: \(ReplateCameraView.spheresModels.count)")
718
+ return false
719
+ }
720
+ mesh = ReplateCameraView.spheresModels[72 + sphereIndex]
721
+
722
+ if ReplateCameraView.upperSpheresSet.allSatisfy({ $0 }) {
723
+ callback = ReplateCameraController.completedUpperSpheresCallback
724
+ ReplateCameraController.completedUpperSpheresCallback = nil
725
+ }
726
+ }
727
+ } else if deviceTargetInFocus == 0 {
728
+ if sphereIndex >= ReplateCameraView.lowerSpheresSet.count {
729
+ print("[updateSpheres] Lower sphere index out of range. Index: \(sphereIndex), Count: \(ReplateCameraView.lowerSpheresSet.count)")
730
+ return false
731
+ }
732
+
733
+ if !ReplateCameraView.lowerSpheresSet[sphereIndex] {
734
+ ReplateCameraView.lowerSpheresSet[sphereIndex] = true
735
+ ReplateCameraView.photosFromDifferentAnglesTaken += 1
736
+ newAngle = true
737
+
738
+ if sphereIndex >= ReplateCameraView.spheresModels.count {
739
+ print("[updateSpheres] Lower spheresModels index out of range. Index: \(sphereIndex), Count: \(ReplateCameraView.spheresModels.count)")
740
+ return false
741
+ }
742
+ mesh = ReplateCameraView.spheresModels[sphereIndex]
743
+
744
+ if ReplateCameraView.lowerSpheresSet.allSatisfy({ $0 }) {
745
+ callback = ReplateCameraController.completedLowerSpheresCallback
746
+ ReplateCameraController.completedLowerSpheresCallback = nil
747
+ }
667
748
  }
668
749
  }
750
+
751
+ if let mesh = mesh {
752
+ let material = SimpleMaterial(color: .green, roughness: 1, isMetallic: false)
753
+ mesh.model?.materials[0] = material
754
+ ReplateCameraView.generateImpactFeedback(strength: .light)
755
+ }
756
+
757
+ // Ensure callback execution doesn't interfere with array access
758
+ guard let validCallback = callback else {
759
+ return newAngle
760
+ }
761
+ validCallback([])
762
+
763
+ return newAngle
669
764
  }
670
-
671
- if let mesh = mesh {
672
- let material = SimpleMaterial(color: .green, roughness: 1, isMetallic: false)
673
- mesh.model?.materials[0] = material
674
- ReplateCameraView.generateImpactFeedback(strength: .light)
675
- }
676
-
677
- callback?([])
678
-
679
- return newAngle
680
765
  }
681
766
 
682
767
  static func angleBetweenAnchorXAndCamera(anchor: AnchorEntity, cameraTransform: simd_float4x4) -> Float {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replate-camera",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "Camera component for Replate Manager",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",