You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在RealityKit中实现两三角形旋转动画时保持顶点连接的方法

实现RealityKit中三角形旋转时保持顶点连接的动画

动画效果示例

我正在使用RealityKit开发功能,想要实现红色三角形旋转时与黑色三角形保持共享顶点接触的动画效果。以下是我的当前代码:

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)
            }))
}

可以实现这种保持顶点连接的动画,核心是围绕共享顶点做旋转动画,而非直接修改整个实体的transform(当前代码的平移+旋转会导致顶点脱离)。下面是两种可行方案:

方案一:修改锚点(Pivot)设置旋转中心

实体默认旋转中心是自身几何中心,你需要把红色三角形的旋转中心设置为和黑色三角形共享的顶点位置,旋转时自然会保持顶点接触。

修改代码步骤:

  1. 计算共享顶点的世界坐标:黑色三角形底部两个顶点为[-0.1, -0.1, 0]和[0.1, -0.1, 0](因缩放了0.1),红色三角形顶部顶点与这两个点重合。
  2. 将红色三角形的pivot设置为其中一个共享顶点(比如左侧的[-0.1, -0.1, 0])。
  3. 动画时仅修改旋转属性,无需平移即可保持顶点连接。

修改后的关键代码:

// 在添加secondEntity到content之前设置pivot
let sharedVertexWorldPosition: SIMD3<Float> = [-0.1, -0.1, 0]
// 将世界坐标转换为红色三角形的本地坐标
let localPivot = secondEntity.convert(position: sharedVertexWorldPosition, from: nil)
// 设置pivot:偏移量为localPivot,旋转缩放保持默认
secondEntity.transform.pivot = Transform(scale: .one, rotation: .identity, translation: localPivot)

// 手势中的动画修改为仅旋转
.gesture(TapGesture()
        .targetedToAnyEntity()
        .onEnded({ tap in
            let tapentity = tap.entity
            var transform = tapentity.transform
            // 围绕共享顶点旋转π弧度
            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)
        }))

方案二:关键帧动画手动计算每帧位置

如果需要更精细的控制,可通过关键帧动画手动计算旋转过程中红色三角形的每个位置,确保共享顶点始终与黑色三角形接触。

核心思路:

  • 定义旋转的起始和结束角度
  • 对每个关键帧,计算红色三角形围绕共享顶点旋转后的位置与姿态
  • 将这些关键帧组合成动画资源

示例代码片段:

.gesture(TapGesture()
        .targetedToAnyEntity()
        .onEnded({ tap in
            let tapentity = tap.entity
            let sharedVertex = SIMD3<Float>(-0.1, -0.1, 0)
            let startRotation = tapentity.transform.rotation
            let endRotation = simd_quatf(angle: -.pi, axis: [1,0,0])
            
            // 创建关键帧动画
            var keyframes: [Float: Transform] = [:]
            for t in stride(from: 0.0, to: 1.0, by: 0.1) {
                let interpolatedRotation = simd_slerp(startRotation, endRotation, t)
                // 计算旋转后的实体位置:保持共享顶点不动,实体围绕该点旋转
                let localOffset = tapentity.transform.translation - sharedVertex
                let rotatedOffset = interpolatedRotation.act(localOffset)
                let newTranslation = sharedVertex + rotatedOffset
                
                keyframes[t] = Transform(translation: newTranslation, rotation: interpolatedRotation, scale: tapentity.transform.scale)
            }
            keyframes[1.0] = Transform(translation: sharedVertex + endRotation.act(tapentity.transform.translation - sharedVertex), rotation: endRotation, scale: tapentity.transform.scale)
            
            let animationResource = try! AnimationResource.generate(with: keyframes)
            tapentity.playAnimation(animationResource)
        }))

注意事项

  • 确保初始状态下两个三角形的共享顶点完全重合,否则旋转时会出现偏移
  • 使用convert(position:from:)方法正确转换坐标空间,避免世界坐标与本地坐标混淆
  • 方案一更简洁高效,适合大多数场景;方案二更灵活,适合需要自定义运动轨迹的情况

内容的提问来源于stack exchange,提问作者Francisco García

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:34:51