swiftuirealitykitvisionos

How to maintain vertex connection during rotation animation between two triangles in RealityKit?


enter image description here

I'm working with RealityKit and trying to create an animation where a red triangle rotates while maintaining contact with a black triangle at their shared vertices. Here's my current code:

struct TestView: View {

var body: some View {
    RealityView {content in
        
    
        var material = PhysicallyBasedMaterial()
        material.baseColor = SimpleMaterial.BaseColor(tint: .pink1)
        material.faceCulling = .none
        
        var firstDescription = MeshDescriptor(name: "firstDescription")
        firstDescription.positions = MeshBuffers.Positions(
          [[-1, -1, 0], [1, -1, 0], [0, 1, 0]]
        )
        
        firstDescription.primitives = .triangles([0, 1, 2])
        let firstEntity = ModelEntity(
           mesh: try! .generate(from: [firstDescription]),
           materials: [SimpleMaterial(color: .black, isMetallic: false)]
        )
        firstEntity.scale = .init(x: 0.1, y: 0.1, z: 0.1)
        content.add(firstEntity)
        
        
        var secondDescription = MeshDescriptor(name: "secondDescription")
        secondDescription.positions = MeshBuffers.Positions(
          [[-1, -1, 0], [0, -3, 0], [1, -1, 0]]
        )
        
        secondDescription.primitives = .triangles([0, 1, 2])
        let secondEntity = ModelEntity(
           mesh: try! .generate(from: [secondDescription]),
           materials: [material]
        )
        secondEntity.scale = .init(x: 0.1, y: 0.1, z: 0.1)
        
        secondEntity.generateCollisionShapes(recursive: true)
        secondEntity.components[InputTargetComponent.self] = InputTargetComponent()
        content.add(secondEntity)
    }
    .gesture(TapGesture()
            .targetedToAnyEntity()
            .onEnded({ tap in
                let tapentity = tap.entity
                
                var transform = tapentity.transform
                transform.translation = [0, -0.2, 0.001]
                transform.rotation = simd_quatf(angle: -.pi, axis: [1,0,0])
            
                let animationDefinition = FromToByAnimation(to: transform,
                                                            bindTarget: .transform)
                let animationViewDefinition = AnimationView(source: animationDefinition,
                                                            delay: 0,
                                                            speed: 0.5)
                let animationResource = try! AnimationResource.generate(with: animationViewDefinition)
                tapentity.playAnimation(animationResource)
            }))
}

}

Is there a way to define an animation that maintains the vertex connection between both triangles during the rotation? Maybe using a different animation approach or calculating intermediate positions?


Solution

  • If the imaginary axis around which the rotation occurs (in this case I mean the X axis) will be located at a height of Y=0, then the rotation will be expected, i.e. during the animation there will be no separation of the base of one triangle from another. To do this, I changed the Y-axis location by +1 meter for each vertex.

    firstDescription.positions = .init([
        [-1, 0, .zero], [1, 0, .zero], [0, 2, .zero]
    ])
    
    secondDescription.positions = .init([
        [-1, 0, .zero], [0, -2, .zero], [1, 0, .zero]
    ])
    

    enter image description here

    Also, I placed both triangles in a parent group so that they could be moved around in space.

    import SwiftUI
    import RealityKit
    
    struct TestView: View {
        @State var pbr = PhysicallyBasedMaterial()
        let parent = Entity()
        
        var body: some View {
            RealityView { rvc in
                var firstDescription = MeshDescriptor(name: "first")
                firstDescription.positions = .init(
                    [[-1, 0, .zero], [1, 0, .zero], [0, 2, .zero]]
                )
                firstDescription.primitives = .triangles([0, 1, 2])
                
                let firstModel = ModelEntity(
                    mesh: try! .generate(from: [firstDescription]),
                    materials: [UnlitMaterial(color: .black)]
                )
                firstModel.scale /= 5
                parent.addChild(firstModel)
                parent.position = [0, 1,-1]
                rvc.add(parent)
                
                var secondDescription = MeshDescriptor(name: "second")
                secondDescription.positions = .init(
                    [[-1, 0, .zero], [0, -2, .zero], [1, 0, .zero]]
                )
                secondDescription.primitives = .triangles([0, 1, 2])
                
                pbr.baseColor = .init(tint: .red)
                pbr.faceCulling = .none
                
                let secondModel = ModelEntity(
                    mesh: try! .generate(from: [secondDescription]),
                    materials: [pbr]
                )
                secondModel.scale /= 5
                secondModel.generateCollisionShapes(recursive: false)
                secondModel.components[InputTargetComponent.self] = .init()
                parent.addChild(secondModel)
            }
            .gesture(
                TapGesture()
                    .targetedToAnyEntity()
                    .onEnded {
                        let entity = $0.entity
                        var transform = entity.transform
                        transform.rotation = .init(angle: -.pi, axis: [1,0,0])
                        
                        let definition = FromToByAnimation(
                            to: transform, bindTarget: .transform
                        )
                        let animationView = AnimationView(
                            source: definition, speed: 1.0
                        )
                        let resource = try! AnimationResource.generate(
                            with: animationView
                        )
                        entity.playAnimation(resource)
                    }
            )
        }
    }