Machineboy空
RealityKit 입문 - Physics 본문
PhysicsBodyComponent, generateCollisionShapes
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
/// 땅에 콜라이더 설치
let planeAnchorEntity = AnchorEntity(plane: .horizontal)
let plane = ModelEntity(mesh: MeshResource.generatePlane(width: 1, height: 1), materials: [SimpleMaterial(color: .orange, isMetallic: true)])
plane.transform.rotation = simd_quatf(angle: .pi / 2, axis: SIMD3(1,0,0))
/// static: 떨어지지 않음
plane.physicsBody = PhysicsBodyComponent(massProperties: .default,material: .generate(), mode: .static)
plane.generateCollisionShapes(recursive: true)
planeAnchorEntity.addChild(plane)
arView.scene.addAnchor(planeAnchorEntity)
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
context.coordinator.view = arView
return arView
}
class Coordinator: NSObject {
weak var view: ARView?
@objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = view else { return }
let location = recognizer.location(in: view)
let results = view.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
if let result = results.first {
let anchorEntity = AnchorEntity(raycastResult: result)
let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.3), materials: [SimpleMaterial(color: .green, isMetallic: false)])
/// default는 1m지름의 sphere로 약 1kg 정도임
/// dynamic, static, kinematic
/// static: moved될 수 없다는 뜻이다.
/// dynamic: 움직일 수 있다.
box.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .generate(), mode: .dynamic)
box.generateCollisionShapes(recursive: true)
box.position = simd_make_float3(0,0.7,0)
anchorEntity.addChild(box)
view.scene.anchors.append(anchorEntity)
}
}
}
Collision Detection (1) - CollisionEvents.Began, Updated, Ended
/// 투명한 바닥 콜라이더 설치
let floorAnchor = AnchorEntity(plane: .horizontal)
let floor = ModelEntity(mesh: MeshResource.generateBox(size: [1000,0,1000]), materials: [OcclusionMaterial()])
floor.generateCollisionShapes(recursive: true)
floor.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .static)
floorAnchor.addChild(floor)
arView.scene.addAnchor(floorAnchor)
// CollisionEvent - Began과 Ended 체크
class Coordinator: NSObject, ARSessionDelegate {
weak var view: ARView?
var collisionSubscriptions = [Cancellable]()
@objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = view else { return }
let location = recognizer.location(in: view)
let results = view.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
if let result = results.first {
let anchorEntity = AnchorEntity(raycastResult: result)
let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: false)])
box.position.y = 0.3
box.generateCollisionShapes(recursive: true)
box.physicsBody = PhysicsBodyComponent(massProperties: .default, mode: .dynamic)
/// mode : trigger
/// filter: sensor = particular model entity can collide
box.collision = CollisionComponent(shapes: [.generateBox(size: [0.2,0.2,0.2])], mode: .trigger, filter: .sensor)
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self){ event in
/// entityA , entityB: box와 floor가 A와 B로 인식
///event.entityA.removeFromParent()
box.model?.materials = [SimpleMaterial(color: .purple, isMetallic: true)]
})
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self){ event in
box.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
})
box.position = simd_make_float3(0,0.7,0)
anchorEntity.addChild(box)
view.scene.anchors.append(anchorEntity)
}
}
}
Collision Detection (2) - CollisionEvent.entityA, CollisionEvent.entityB
class Coordinator: NSObject, ARSessionDelegate {
weak var view: ARView?
var collisionSubscriptions = [Cancellable]()
func buildEnvironment() {
guard let view = view else { return }
let anchor = AnchorEntity(plane: .horizontal)
let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
box1.generateCollisionShapes(recursive: true)
box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
box2.generateCollisionShapes(recursive: true)
box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
box2.position.z = 0.3
let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
sphere1.generateCollisionShapes(recursive: true)
sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
sphere1.position.x += 0.3
let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
sphere2.generateCollisionShapes(recursive: true)
sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
sphere2.position.x -= 0.3
anchor.addChild(box1)
anchor.addChild(box2)
anchor.addChild(sphere1)
anchor.addChild(sphere2)
view.scene.addAnchor(anchor)
view.installGestures(.all, for: box1)
view.installGestures(.all, for: box2)
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self ){ event in
guard let entity1 = event.entityA as? ModelEntity,
let entity2 = event.entityB as? ModelEntity else { return }
/// entity A와 B가 결정되는 기준과, AB의 역할은 딱히 중요하지 않은건가..?
entity1.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
entity2.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
})
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self ){ event in
guard let entity1 = event.entityA as? ModelEntity,
let entity2 = event.entityB as? ModelEntity else { return }
entity1.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
entity2.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
})
}
}
Collision Detection (3) - Collision Filters, CollisionGroup
같은 collisionMask끼리만 충돌할 수 있다!
//CollisionComponent (group, mask)
sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
import Foundation
import ARKit
import RealityKit
import Combine
/// want to get a notification whenever a collision is happening between the two model entities
class Coordinator: NSObject, ARSessionDelegate {
weak var view: ARView?
var collisionSubscriptions = [Cancellable]()
/// CollisionGroup : Layer 설정
let boxGroup = CollisionGroup(rawValue: 1 << 0)
let sphereGroup = CollisionGroup(rawValue: 1 << 1)
func buildEnvironment() {
guard let view = view else { return }
// create masks : box끼리, sphere끼리 부딪힘
let boxMask = CollisionGroup.all.subtracting(sphereGroup)
let sphereMask = CollisionGroup.all.subtracting(boxGroup)
let anchor = AnchorEntity(plane: .horizontal)
let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
box1.generateCollisionShapes(recursive: true)
/// mask: different objects that it can actually collide to
/// if it belongs to the same group and if they ahve the same collision mask, then it means that those particular virtual objects can collide within themselves
box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .init(group: boxGroup, mask: boxMask))
let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
box2.generateCollisionShapes(recursive: true)
box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .init(group: boxGroup, mask: boxMask))
box2.position.z = 0.3
let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
sphere1.generateCollisionShapes(recursive: true)
sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
sphere1.position.x += 0.3
let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
sphere2.generateCollisionShapes(recursive: true)
sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
sphere2.position.x -= 0.3
anchor.addChild(box1)
anchor.addChild(box2)
anchor.addChild(sphere1)
anchor.addChild(sphere2)
view.scene.addAnchor(anchor)
view.installGestures(.all, for: box1)
view.installGestures(.all, for: box2)
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self ){ event in
guard let entity1 = event.entityA as? ModelEntity,
let entity2 = event.entityB as? ModelEntity else { return }
/// entity A와 B가 결정되는 기준과, AB의 역할은 딱히 중요하지 않은건가..?
entity1.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
entity2.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
})
collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self ){ event in
guard let entity1 = event.entityA as? ModelEntity,
let entity2 = event.entityB as? ModelEntity else { return }
entity1.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
entity2.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
})
}
}
Collision Detection and Physics
import SwiftUI
import RealityKit
import ARKit
struct ContentView : View {
var body: some View {
return ARViewContainer().edgesIgnoringSafeArea(.all)
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
context.coordinator.view = arView
context.coordinator.buildEnvironment()
return arView
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func updateUIView(_ uiView: ARView, context: Context) {}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
import Foundation
import ARKit
import RealityKit
import Combine
/// want to get a notification whenever a collision is happening between the two model entities
class Coordinator: NSObject, ARSessionDelegate, UIGestureRecognizerDelegate {
weak var view: ARView?
var collisionSubscriptions = [Cancellable]()
/// CollisionGroup : Layer 설정
let boxGroup = CollisionGroup(rawValue: 1 << 0)
let sphereGroup = CollisionGroup(rawValue: 1 << 1)
var movableEntities = [ModelEntity]()
func buildEnvironment() {
guard let view = view else { return }
let anchor = AnchorEntity(plane: .horizontal)
// create a floor
/// plane에 width, height, depth가 있었다니
let floor = ModelEntity(mesh: .generatePlane(width: 1, depth: 1), materials: [SimpleMaterial(color: .blue, isMetallic: true)])
floor.generateCollisionShapes(recursive: true)
floor.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .static)
let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
box1.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .dynamic)
box1.generateCollisionShapes(recursive: true)
/// mask: different objects that it can actually collide to
/// if it belongs to the same group and if they ahve the same collision mask, then it means that those particular virtual objects can collide within themselves
box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
box1.position.y = 0.3
let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
box2.generateCollisionShapes(recursive: true)
box2.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .dynamic)
box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
box2.position.z = 0.3
box2.position.y = 0.3
/// filter: .sensor 은 아무거나 충돌
let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
sphere1.generateCollisionShapes(recursive: true)
sphere1.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
sphere1.position.x += 0.3
sphere1.position.y = 0.3
let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
sphere2.generateCollisionShapes(recursive: true)
sphere2.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
sphere2.position.x -= 0.3
sphere2.position.y = 0.3
anchor.addChild(floor)
anchor.addChild(box1)
anchor.addChild(box2)
anchor.addChild(sphere1)
anchor.addChild(sphere2)
movableEntities.append(box1)
movableEntities.append(box2)
movableEntities.append(sphere1)
movableEntities.append(sphere2)
view.scene.addAnchor(anchor)
movableEntities.forEach {
view.installGestures(.all, for: $0).forEach {
$0.delegate = self
}
}
setupGestures()
}
fileprivate func setupGestures() {
guard let view = view else { return }
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panned(_:)))
panGesture.delegate = self
view.addGestureRecognizer(panGesture)
}
@objc func panned(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .ended, .cancelled, .failed:
// change the physics mode to dynamic
movableEntities.compactMap {$0}.forEach {
$0.physicsBody?.mode = .dynamic
}
default :
return
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReconizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let translationGesture = gestureRecognizer as? EntityGestureRecognizer,
let entity = translationGesture.entity as? ModelEntity else {
return true
}
entity.physicsBody?.mode = .kinematic
return true
}
}
Refactoring
enum Shape {
case box
case sphere
}
class MovableEntity: Entity, HasModel, HasPhysics, HasCollision {
var size: Float!
var color: UIColor!
var shape: Shape = .box
init(size: Float, color: UIColor, shape: Shape) {
super.init()
self.size = size
self.color = color
self.shape = shape
let mesh = generateMeshResources()
let material = generateMaterial()
model = ModelComponent(mesh: mesh, materials: [material])
physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
collision = CollisionComponent(shapes: [generateShapeResource()], mode:.trigger, filter: .sensor)
generateCollisionShapes(recursive: true)
}
private func generateMaterial() -> Material {
SimpleMaterial(color:color, isMetallic: true)
}
private func generateMeshResources() -> MeshResource{
switch shape {
case .box:
return MeshResource.generateBox(size: size)
case .sphere:
return MeshResource.generateSphere(radius: size)
}
}
private func generateShapeResource() -> ShapeResource {
switch shape {
case .box:
return ShapeResource.generateBox(size: [self.size,self.size,self.size])
case .sphere:
return ShapeResource.generateSphere(radius: self.size)
}
}
@MainActor @preconcurrency required init() {
fatalError("init() has not been implemented")
}
}
'언어 > iOS' 카테고리의 다른 글
Realitykit 입문 - Persistence (0) | 2024.10.15 |
---|---|
RealityKit 입문 - 측정앱 만들기 (0) | 2024.10.15 |
RealityKit 입문 - ARCouchingView 더하기 (0) | 2024.10.14 |
RealityKit 입문 - Material 종류들 (Occulusion, Unlit and Video Materials) (4) | 2024.10.13 |
RealityKit 입문 - Reality Composer (0) | 2024.10.13 |