Como Criar uma Forma 3D no RealityKit para o visionOS a partir de um Shape 2D do SwiftUI
Olá, pessoal! 👋 Hoje, quero compartilhar com vocês uma maneira super interessante de transformar um Shape (2D) criado com SwiftUI em um modelo 3D para o RealityKit
no visionOS.
Nesse tutorial, iremos fazer uso de um recurso fantástico que a Apple introduziu à API do RealityKit no visionOS 2.0, que permite criar um MeshResource à partir de um Path: o MeshResource(extruding:extrusionOptions:)
.
Com ele você pode adicionar profundidade e criar um Mesh (malha) do objeto 3D resultante à partir de qualquer Path
, incluindo textos em AttributedString
.
Vamos ao nosso passo-a-passo:
Passo 1: Requisitos
Como esse recurso foi adicionado ao visionOS 2.0, você precisará:
- Xcode 16 beta
- visionOS 2.0
Passo 2: Criando um Shape 2D no SwiftUI
Para criar nosso shape 2D, vamos utilizar Circle
s do SwiftUI.
Para obter um formato de tubo, temos que criar um formato de círculo com um furo no meio, que será o interior do nosso tubo.
Com SwiftUI é muito fácil fazer isso:
let diameter = CGFloat(radius * 2.0)
let internalDiameter = diameter - CGFloat(thick * 2.0)
let innerShape = Circle().size(width: internalDiameter, height: internalDiameter)
let roundShape = Circle().size(width: diameter, height: diameter)
let hollowCircle = roundShape
.symmetricDifference(innerShape.offset(x: CGFloat(thick), y: CGFloat(thick)))
Passo 3: Convertendo o Shape 2D em Mesh 3D
Para converter nosso shape 2D em um objeto 3D, vamos usar o `extruding` do MeshResource.
var options = MeshResource.ShapeExtrusionOptions()
options.extrusionMethod = .linear(depth: height)
let mesh = try await MeshResource(
extruding: hollowCircle.path(
in:CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)
),
extrusionOptions: options
)
No código acima, nós fazemos duas coisas que são fundamentais para criar o objeto:
- Criamos um
ShapeExtrusionOptions
para dizermos qualextrusionMethod
nós queremos. Nesse caso, iremos usar o.linear
, que receberá o valor de profundidade (depth
). No exemplo, a profundidade do tubo pode ser considerada a sua altura, pois é a altura que teremos ao colocá-lo em pé. - Criamos um
MeshResource
, realizando oextruding
à partir do path do nosso círculo oco (hollowCircle
) e informando as opções que queremos para o procedimento de extruding.
Passo 4: Criando e configurando nosso ModelEntity
Agora que temos nosso shape convertido em uma MeshResource
, podemos usá-lo para criar um ModelEntity
e configurar seu corpo físico e sua estrutura de colisão.
let entity = ModelEntity(mesh: mesh, materials: [SimpleMaterial(color: .gray, isMetallic: false)])
let shape = try await ShapeResource.generateStaticMesh(from: mesh)
let colision = CollisionComponent(shapes: [shape], isStatic: false)
entity.components.set(colision)
entity.components.set(PhysicsBodyComponent(
massProperties: .default,
mode: .static
))
Passo 5 (Opcional): Deixando nosso tubo em pé
Se você quiser que seu tubo fique na vertical, e não na horizontal, você pode simplesmente movê-lo para deixá-lo na posição correta:
entity.move(to: simd_float4x4(simd_quatf(.init(angle: Angle2D(degrees: 90), axis: .x))), relativeTo: nil)
Nossa implementação final
Ao final, teremos uma implementação semelhante à essa:
import RealityKit
import SwiftUI
extension ModelEntity {
@MainActor
enum Tube {
enum Position {
case vertical
case horizontal
}
static func generateTube(
radius: Float,
thick: Float,
height: Float,
position: Tube.Position = .horizontal
) async throws -> some Entity {
let diameter = CGFloat(radius * 2.0)
let internalDiameter = diameter - CGFloat(thick * 2.0)
let innerShape = Circle().size(width: internalDiameter, height: internalDiameter)
let roundShape = Circle().size(width: diameter, height: diameter)
let hollowCircle = roundShape
.symmetricDifference(innerShape.offset(x: CGFloat(thick), y: CGFloat(thick)))
var options = MeshResource.ShapeExtrusionOptions()
options.extrusionMethod = .linear(depth: height)
let mesh = try await MeshResource(
extruding: hollowCircle.path(
in:CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)
),
extrusionOptions: options
)
let entity = ModelEntity(mesh: mesh, materials: [SimpleMaterial(color: .gray, isMetallic: false)])
let shape = try await ShapeResource.generateStaticMesh(from: mesh)
let collision = CollisionComponent(shapes: [shape], isStatic: false)
entity.components.set(collision)
entity.components.set(PhysicsBodyComponent(
massProperties: .default,
mode: .static
))
if position == .vertical {
entity.move(to: simd_float4x4(simd_quatf(.init(angle: Angle2D(degrees: 90), axis: .x))), relativeTo: nil)
}
return entity
}
}
}
Quer testar aí?
Todo o código e a aplicação dele em um projeto demo está disponível no meu GitHub: https://github.com/salla-andre/TubeExample
E é isso, pessoal! 🎉
Espero que tenham gostado desse pequeno tutorial e que ele seja útil no seu próximo projeto no visionOS. Experimente com diferentes shapes e materiais para criar suas próprias experiências imersivas. Até a próxima!