Canvatorium Visio Lab 5040
Create 3D Text using MeshResource
Based on the sample code from Creating a spatial drawing app with RealityKit.
I used a modified version of this in the prototype for Looming Deadlines.
struct Lab5040: View {
@State var textValue: String = "3D Text!"
var body: some View {
ZStack {
GeometryReader3D { proxy in
Lab5040TextMesh(textValue: $textValue, proxy: proxy)
.frame(idealHeight: 30)
}
}
.frame(depth: 100, alignment: DepthAlignment.back)
}
}
private struct Lab5040TextMesh: View {
@Binding var textValue: String
var proxy: GeometryProxy3D
@State private var modelEntity: ModelEntity?
var body: some View {
RealityView { content in
if modelEntity == nil {
let entity = makeTextEntity(textValue: textValue)
modelEntity = entity
entity.scaleToFit(proxy: proxy, content: content)
content.add(entity)
}
} update: { content in
for entity in content.entities {
entity.scaleToFit(proxy: proxy, content: content)
}
}
}
@MainActor private let faceMaterial: PhysicallyBasedMaterial = {
var faceMaterial = PhysicallyBasedMaterial()
faceMaterial.metallic = .init(floatLiteral: 1)
faceMaterial.baseColor = .init(tint: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1))
return faceMaterial
}()
@MainActor private let borderMaterial: PhysicallyBasedMaterial = {
var borderMaterial = PhysicallyBasedMaterial()
borderMaterial.metallic = .init(floatLiteral: 0.15)
borderMaterial.roughness = .init(floatLiteral: 0.85)
borderMaterial.baseColor = .init(tint: #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1))
return borderMaterial
}()
private func makeTextEntity(textValue: String) -> ModelEntity {
var textString = AttributedString(textValue)
textString.font = .systemFont(ofSize: 8.0, weight: .black)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
textString.mergeAttributes(AttributeContainer([.paragraphStyle: paragraphStyle]))
var textOptions = MeshResource.GenerateTextOptions()
textOptions.containerFrame = CGRect(x: 0, y: 0, width: 100, height: 50)
var extrusionOptions = MeshResource.ShapeExtrusionOptions()
extrusionOptions.extrusionMethod = .linear(depth: 1)
extrusionOptions.materialAssignment = .init(front: 0, back: 0, extrusion: 1, frontChamfer: 1, backChamfer: 1)
extrusionOptions.chamferRadius = 0.1
let textMesh = try! MeshResource(extruding: textString,
textOptions: textOptions,
extrusionOptions: extrusionOptions)
return ModelEntity(mesh: textMesh, materials: [faceMaterial, borderMaterial])
}
}
private extension Entity {
func scaleToFit(proxy: GeometryProxy3D, content: RealityViewContent, scaler: Float = 0.8, yOffset: Float = 0.0) {
guard let model = components[ModelComponent.self] else { return }
let frame = proxy.frame(in: .local)
let frameCenter = content.convert(frame.center, from: .local, to: .scene)
let frameSize = abs(content.convert(frame.size, from: .local, to: .scene))
let bounds = model.mesh.bounds
let extents = bounds.extents
let center = bounds.center
let graphicScale = min(frameSize.x / extents.x, frameSize.y / extents.y)
scale = SIMD3<Float>(repeating: graphicScale * scaler)
position = frameCenter - SIMD3<Float>(0, 0 + yOffset, 0)
position -= center * (graphicScale * scaler)
}
}