Blender4iOS/Sources/GreasePencil/GPCanvasView.swift

113 lines
3.7 KiB
Swift

// GPCanvasView.swift
// Composites the Metal viewport with a PencilCaptureOverlay on top.
// Pencil strokes are projected into 3D and drawn by the renderer.
import SwiftUI
import simd
struct GPCanvasView: View {
let renderer: ViewportRenderer
@State private var gpData = GPObjectData()
@State private var activeStroke: GPStroke?
@State private var brushColor: Color = .black
@State private var brushWidth: Float = 0.05
var body: some View {
GeometryReader { geo in
ZStack {
// 3D viewport (renders scene + finalized GP strokes)
ViewportPane(renderer: renderer, label: "GP Canvas")
// Pencil input overlay
PencilCaptureOverlay(
onStrokeBegan: {
beginStroke()
},
onSample: { sample in
addSample(sample, viewportSize: geo.size)
},
onStrokeEnded: {
finalizeStroke()
}
)
.allowsHitTesting(true)
// Drawing indicator
if activeStroke != nil {
VStack {
HStack {
Spacer()
Label("Drawing", systemImage: "pencil.tip")
.font(.caption2)
.foregroundStyle(.orange)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(.black.opacity(0.6), in: Capsule())
.padding(8)
}
Spacer()
}
}
}
}
}
// MARK: - Stroke lifecycle
private func beginStroke() {
let stroke = GPStroke()
stroke.color = colorToSIMD(brushColor)
stroke.baseWidth = brushWidth
activeStroke = stroke
}
private func addSample(_ sample: PencilSample, viewportSize: CGSize) {
guard let stroke = activeStroke,
let device = renderer.device else { return }
let projector = StrokeProjector(
viewMatrix: renderer.camera.viewMatrix,
projectionMatrix: MathUtils.perspective(
fovYRadians: .pi / 4,
aspect: Float(viewportSize.width / viewportSize.height),
near: 0.1, far: 500
),
viewportSize: SIMD2<Float>(
Float(viewportSize.width),
Float(viewportSize.height)
),
drawingPlaneNormal: drawingPlaneNormal(),
drawingPlanePoint: renderer.camera.target
)
stroke.addSample(sample, projector: projector)
// Live tessellation for preview
stroke.tessellate(device: device)
}
private func finalizeStroke() {
guard let stroke = activeStroke,
stroke.points.count >= 2 else {
activeStroke = nil
return
}
// Commit to the active layer
gpData.activeLayer?.strokes.append(stroke)
activeStroke = nil
}
// MARK: - Helpers
/// Drawing plane faces the camera (billboard mode).
private func drawingPlaneNormal() -> SIMD3<Float> {
normalize(renderer.camera.eye - renderer.camera.target)
}
private func colorToSIMD(_ color: Color) -> SIMD4<Float> {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
UIColor(color).getRed(&r, green: &g, blue: &b, alpha: &a)
return SIMD4<Float>(Float(r), Float(g), Float(b), Float(a))
}
}