Machineboy空
RealityKit의 Gesture를 살펴보자 - Pinch, Scale 본문
ARView에 큐브를 넣고 여러 가지 제스처를 통해 그 큐브를 살펴보는 기능을 만드려고 한다.
EntityGestures의 종류 - 기본으로 제공하는 제스처
Realitykit에서 제공하는 기본 제스처들은 다음과 같다.
- rotation: 멀티 터치로 물체 회전
- scale: pinch 동작으로 물체 스케일 조절
- translation: 싱글 터치로 물체 이동
https://developer.apple.com/documentation/realitykit/arview/entitygestures
ARView.EntityGestures | Apple Developer Documentation
The set of possible entity gesture recognizers.
developer.apple.com
EntityGestures 코드
import SwiftUI
import ARKit
import RealityKit
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
// cameraMode: .nonAR
let arView = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false)
arView.environment.background = .color(.white)
let cubeMaterial = SimpleMaterial(color: .orange, isMetallic: false)
let cubeMesh = MeshResource.generateBox(size: 0.3)
let cubeModel = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
// Collision이 있어야 충돌 체크 가능
cubeModel.generateCollisionShapes(recursive: true)
let cubeAnchor = AnchorEntity(world: [0, 0, 0])
cubeAnchor.addChild(cubeModel)
arView.scene.anchors.append(cubeAnchor)
// Gesture 적용
arView.installGestures(.all, for: cubeModel)
// .nonAR일 때 가상 카메라가 있어야 화면 보임
let camera = PerspectiveCamera()
camera.position = [0, 0.5, 1]
// relativeTo nil하게 회전
camera.look(at: cubeAnchor.position, from: camera.position, relativeTo: nil)
let cameraAnchor = AnchorEntity(world: [0, 0, 0])
cameraAnchor.addChild(camera)
arView.scene.addAnchor(cameraAnchor)
return arView
}
func updateUIView(_ uiView: UIViewType, context: Context) { }
}
#Preview {
ContentView()
}
EntityGestures 의 한계
우선 나는 물체를 이동시킬 필요가 없기 때문에 translation 제스처가 필요없다.
rotation의 경우 Y축 기준으로만 회전하는데, 난 상하로도 즉 X축 기준으로도 회전시킬 필요가 있다.
그리고 rotation과 scale은 동일한 multitouch로 작동하여 동작을 구분하기 어렵다.
Custom Gesture 1 - EntityGesture.scale에 제한 범위 주기!
큐브가 gesture를 통해 조절되는 스케일의 최대, 최소 범위를 규정하고 싶다!
일정 사이즈 이상 커지지 않았으면 좋겠고, 일정 사이즈 이하로 작아지지 않았으면 좋겠다는 것!
제스처에 따른 크기 변화를 감지하는 방법 1
- Combine을 활용해 SceneEvent를 구독하도록 설정하고
- updateUIView에서는 따로 동작을 주지 않는다.
import SwiftUI
import ARKit
import RealityKit
import Combine
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
// cameraMode: .nonAR
let arView = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false)
arView.environment.background = .color(.white)
let cubeMaterial = SimpleMaterial(color: .orange, isMetallic: false)
let cubeMesh = MeshResource.generateBox(size: 0.3)
let cubeModel = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
// Collision이 있어야 충돌 체크 가능
cubeModel.generateCollisionShapes(recursive: true)
let cubeAnchor = AnchorEntity(world: [0, 0, 0])
cubeAnchor.addChild(cubeModel)
arView.scene.anchors.append(cubeAnchor)
// coordinator에 저장
context.coordinator.cubeModel = cubeModel
// Gesture 적용
arView.installGestures(.scale, for: cubeModel)
// .nonAR일 때 가상 카메라가 있어야 화면 보임
let camera = PerspectiveCamera()
camera.position = [0, 0.5, 1]
camera.look(at: cubeAnchor.position, from: camera.position, relativeTo: nil)
let cameraAnchor = AnchorEntity(world: [0, 0, 0])
cameraAnchor.addChild(camera)
arView.scene.addAnchor(cameraAnchor)
// 변화 감지 방법1: sceneEvent를 구독하도록 설정
context.coordinator.cancellable = arView.scene.subscribe(to: SceneEvents.Update.self) { _ in
context.coordinator.clampModelSize()
} as? AnyCancellable
return arView
}
func updateUIView(_ uiView: UIViewType, context: Context) { }
func makeCoordinator() -> Coordinator {
return Coordinator()
}
}
class Coordinator: NSObject {
var cubeModel: ModelEntity?
var cancellable: AnyCancellable? // 구독 취소 가능
func clampModelSize() {
guard let model = cubeModel else { return }
let currentScale = model.scale.x
var newScale = currentScale
if currentScale < 0.5 {
newScale = max(currentScale, 0.5)
}
if currentScale > 1.0 {
newScale = min(currentScale, 1.0)
}
if newScale != currentScale {
model.scale = SIMD3(repeating: newScale)
}
}
}
#Preview {
ContentView()
}
제스처에 따른 크기 변화를 감지하는 방법 2
- updateUIView함수를 활용해 보기
- 이 코드는 왜 작동을 하지 않는가
난 updateUIView가 매 프레임 호출되는 Unity의 Update구문과 비슷한 것이겠거니 생각했다.
updateUIView 함수 내부에 print를 찍어보니 scale이 변화되어도 호출되지 않는데,
무언가 uiView에 anchor가 추가되거나 내부 로직에서 감지하는 큰 이벤트가 일어날 때만 뷰를 update해주는 함수였던 것이다.
공식문서를 찾아봤으나 구체적으로 어떤 이벤트를 감지하는지에 관해서는 찾을 수가 없었다.
따라서, 제스처 변화에 따른 scale 변화를 감지해 뷰를 새로 그려주려면 방법 1번 처럼 uiView내의 sceneEvent를 따로 구독해줘야 하나보다.
import SwiftUI
import ARKit
import RealityKit
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
struct ARViewContainer: UIViewRepresentable {
var currentScale: SIMD3<Float> = [0,0,0]
func makeUIView(context: Context) -> some UIView {
// cameraMode: .nonAR
let arView = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false)
arView.environment.background = .color(.white)
let cubeMaterial = SimpleMaterial(color: .orange, isMetallic: false)
let cubeMesh = MeshResource.generateBox(size: 0.3)
let cubeModel = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
// Collision이 있어야 충돌 체크 가능
cubeModel.generateCollisionShapes(recursive: true)
let cubeAnchor = AnchorEntity(world: [0, 0, 0])
cubeAnchor.addChild(cubeModel)
arView.scene.anchors.append(cubeAnchor)
// coordinator에 저장
context.coordinator.cubeModel = cubeModel
// Gesture 적용
arView.installGestures(.scale, for: cubeModel)
// .nonAR일 때 가상 카메라가 있어야 화면 보임
let camera = PerspectiveCamera()
camera.position = [0, 0.5, 1]
camera.look(at: cubeAnchor.position, from: camera.position, relativeTo: nil)
let cameraAnchor = AnchorEntity(world: [0, 0, 0])
cameraAnchor.addChild(camera)
arView.scene.addAnchor(cameraAnchor)
return arView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
guard let cubeModel = context.coordinator.cubeModel else { return }
let clampedScale = max(min(currentScale.x, 1.0), 0.5)
cubeModel.transform.scale = SIMD3<Float>(repeating: clampedScale)
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
}
class Coordinator: NSObject {
var cubeModel: ModelEntity?
}
#Preview {
ContentView()
}
'언어 > iOS' 카테고리의 다른 글
RealityKit - TextField에 입력한 글자를 3D Entity로 출력하기! (0) | 2024.11.11 |
---|---|
RealityKit의 Gesture를 살펴보자 - Pan, Rotate (0) | 2024.11.11 |
비콘이 감지되면 버튼을 활성화 시키자! 여러 개의 비컨 감지 로직 (0) | 2024.11.03 |
비컨별로 다른 화면 띄우기! (0) | 2024.11.01 |
아이폰 2대 간 거리 탐지하기, iBeacon (6) | 2024.10.31 |