feat(av-live-body): HUD + menus + pose -> scene
C - HUD coin haut-gauche + touches actives :
- HUDOverlay.swift : statut mode actif, count personnes, list
touches actives (S/0-9/M/W/C/V), positions head/wrists de la
1ere personne. allowsHitTesting(false).
Menus + raccourcis SwiftUI complets :
- App > Toggle Settings (S)
- Calques > Toggle Webcam (C) / Scene Metal (V) /
Maillage SMPL-X (M) / Fil de fer (W)
- Modes visuels > 0..9 -> storm/.../openpos
Tous propages via NotificationCenter (.toggleSettings,
.toggleLayer, .setVizMode).
B - Pose drive scene uniforms :
- BodyView observe PoseOSCListener en plus de RenderSettings.
- updateNSView pousse pose_count, pose_alive, hand_l_x/y,
hand_r_x/y dans SceneRenderer.uniforms.
- Mode hands3d (#8) reagit a la position des poignets de la
1ere personne detectee. Mode openpos (#9) idem.
A - Dashboard data-only : 0 fix code, le probleme etait
bridge.py orphelins multiples + cache navigateur.
This commit is contained in:
@@ -10,18 +10,51 @@ struct AVLiveBodyApp: App {
|
||||
}
|
||||
.commands {
|
||||
CommandGroup(replacing: .appSettings) {
|
||||
Button("Toggle Settings (S)") {
|
||||
Button("Toggle Settings") {
|
||||
NotificationCenter.default.post(
|
||||
name: .toggleSettings, object: nil)
|
||||
}
|
||||
.keyboardShortcut("s", modifiers: [])
|
||||
}
|
||||
CommandMenu("Calques") {
|
||||
Button("Toggle Webcam") {
|
||||
NotificationCenter.default.post(
|
||||
name: .toggleLayer, object: "camera")
|
||||
}.keyboardShortcut("c", modifiers: [])
|
||||
Button("Toggle Scene Metal") {
|
||||
NotificationCenter.default.post(
|
||||
name: .toggleLayer, object: "scene")
|
||||
}.keyboardShortcut("v", modifiers: [])
|
||||
Button("Toggle Maillage SMPL-X") {
|
||||
NotificationCenter.default.post(
|
||||
name: .toggleLayer, object: "mesh")
|
||||
}.keyboardShortcut("m", modifiers: [])
|
||||
Button("Toggle Fil de fer") {
|
||||
NotificationCenter.default.post(
|
||||
name: .toggleLayer, object: "wireframe")
|
||||
}.keyboardShortcut("w", modifiers: [])
|
||||
}
|
||||
CommandMenu("Modes visuels") {
|
||||
ForEach(0..<10) { i in
|
||||
let names = ["storm", "tunnel", "plasma", "kaleido",
|
||||
"voronoi", "metaballs", "starfield",
|
||||
"bars", "hands3d", "openpos"]
|
||||
Button("\(i) — \(names[i])") {
|
||||
NotificationCenter.default.post(
|
||||
name: .setVizMode, object: i)
|
||||
}.keyboardShortcut(
|
||||
KeyEquivalent(Character(String(i))),
|
||||
modifiers: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let toggleSettings = Notification.Name("avlive.toggleSettings")
|
||||
static let toggleLayer = Notification.Name("avlive.toggleLayer")
|
||||
static let setVizMode = Notification.Name("avlive.setVizMode")
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@@ -30,8 +63,9 @@ struct ContentView: View {
|
||||
@StateObject private var poseListener = PoseOSCListener()
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
BodyView(renderer: renderer, settings: settings)
|
||||
ZStack(alignment: .topLeading) {
|
||||
BodyView(renderer: renderer, settings: settings,
|
||||
poseListener: poseListener)
|
||||
.onAppear {
|
||||
renderer.startOSCServer()
|
||||
poseListener.start()
|
||||
@@ -40,27 +74,56 @@ struct ContentView: View {
|
||||
for: .toggleSettings)) { _ in
|
||||
settings.showPanel.toggle()
|
||||
}
|
||||
// Bouton coin haut-droit pour ouvrir le panel
|
||||
Button(action: { settings.showPanel.toggle() }) {
|
||||
Image(systemName: settings.showPanel
|
||||
? "slider.horizontal.3"
|
||||
: "slider.horizontal.below.rectangle")
|
||||
.font(.system(size: 18, weight: .medium))
|
||||
.padding(10)
|
||||
.background(
|
||||
Circle().fill(Color.black.opacity(0.45)))
|
||||
.foregroundColor(.white)
|
||||
.onReceive(NotificationCenter.default.publisher(
|
||||
for: .toggleLayer)) { note in
|
||||
handleLayerToggle(note)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(
|
||||
for: .setVizMode)) { note in
|
||||
if let n = note.object as? Int { settings.vizMode = n }
|
||||
}
|
||||
|
||||
// HUD coin haut-gauche : mode + touches + pose
|
||||
HUDOverlay(settings: settings, poseListener: poseListener)
|
||||
|
||||
// Bouton settings coin haut-droit
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: { settings.showPanel.toggle() }) {
|
||||
Image(systemName: settings.showPanel
|
||||
? "slider.horizontal.3"
|
||||
: "slider.horizontal.below.rectangle")
|
||||
.font(.system(size: 18, weight: .medium))
|
||||
.padding(10)
|
||||
.background(
|
||||
Circle().fill(Color.black.opacity(0.45)))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(16)
|
||||
.help("Settings (S)")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(16)
|
||||
.help("Settings (S)")
|
||||
|
||||
if settings.showPanel {
|
||||
SettingsPanel(settings: settings)
|
||||
.transition(.move(edge: .trailing)
|
||||
.combined(with: .opacity))
|
||||
HStack {
|
||||
Spacer()
|
||||
SettingsPanel(settings: settings)
|
||||
.transition(.move(edge: .trailing)
|
||||
.combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.18), value: settings.showPanel)
|
||||
}
|
||||
|
||||
private func handleLayerToggle(_ note: Notification) {
|
||||
guard let key = note.object as? String else { return }
|
||||
switch key {
|
||||
case "camera": settings.showCamera.toggle()
|
||||
case "scene": settings.showScene.toggle()
|
||||
case "mesh": settings.showMesh.toggle()
|
||||
case "wireframe": settings.showWireframe.toggle()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct BodyView: NSViewRepresentable {
|
||||
@ObservedObject var renderer: MeshRenderer
|
||||
@ObservedObject var settings: RenderSettings
|
||||
@ObservedObject var poseListener: PoseOSCListener
|
||||
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
let container = NSView(frame: .zero)
|
||||
@@ -114,6 +115,24 @@ struct BodyView: NSViewRepresentable {
|
||||
c.previewLayer?.isHidden = !settings.showCamera
|
||||
c.mtkView?.isHidden = !settings.showScene
|
||||
c.sceneRenderer?.uniforms.viz_mode = Float(settings.vizMode)
|
||||
// Pose -> scene uniforms : drive hands3d (mode 8) et openpos
|
||||
// (mode 9) avec la premiere personne detectee. Les wrists pilotent
|
||||
// hand_l/r ; pose_count alimente bg_fragment.
|
||||
let persons = poseListener.persons
|
||||
c.sceneRenderer?.uniforms.pose_count = Float(persons.count)
|
||||
c.sceneRenderer?.uniforms.pose_alive = persons.isEmpty ? 0 : 1
|
||||
if let first = persons.values.first {
|
||||
// Convertit coords ecran (0..1) -> NDC-ish (-1..1)
|
||||
c.sceneRenderer?.uniforms.hand_l_x = (first.wristL.x - 0.5) * 2
|
||||
c.sceneRenderer?.uniforms.hand_l_y = (first.wristL.y - 0.5) * 2
|
||||
c.sceneRenderer?.uniforms.hand_r_x = (first.wristR.x - 0.5) * 2
|
||||
c.sceneRenderer?.uniforms.hand_r_y = (first.wristR.y - 0.5) * 2
|
||||
} else {
|
||||
c.sceneRenderer?.uniforms.hand_l_x = 0
|
||||
c.sceneRenderer?.uniforms.hand_l_y = 0
|
||||
c.sceneRenderer?.uniforms.hand_r_x = 0
|
||||
c.sceneRenderer?.uniforms.hand_r_y = 0
|
||||
}
|
||||
c.container?.layer?.backgroundColor = NSColor(
|
||||
white: CGFloat(settings.bgBrightness), alpha: 1.0).cgColor
|
||||
c.cameraEntity?.camera.fieldOfViewInDegrees =
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import SwiftUI
|
||||
|
||||
/// HUD coin haut-gauche : mode actif, nb personnes, fps approx, et la
|
||||
/// liste des touches actives (mapping clavier). Translucide, ne capte
|
||||
/// pas les events souris.
|
||||
struct HUDOverlay: View {
|
||||
@ObservedObject var settings: RenderSettings
|
||||
@ObservedObject var poseListener: PoseOSCListener
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
// Statut courant
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "circle.fill")
|
||||
.font(.system(size: 8))
|
||||
.foregroundColor(poseListener.count > 0
|
||||
? .green : .gray)
|
||||
Text("AV-Live-Body").bold()
|
||||
Text("\(settings.vizModeName) (#\(settings.vizMode))")
|
||||
.foregroundColor(.pink)
|
||||
Spacer()
|
||||
Text("pose: \(poseListener.count)")
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
}
|
||||
Divider().background(Color.white.opacity(0.2))
|
||||
|
||||
// Liste des touches actives
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
hudKey("S", "ouvre / ferme le panel reglages")
|
||||
hudKey("0..9", "viz mode (storm / ... / openpos)")
|
||||
hudKey("M", "toggle Maillage SMPL-X")
|
||||
hudKey("W", "toggle Fil de fer")
|
||||
hudKey("C", "toggle Webcam")
|
||||
hudKey("V", "toggle Scene Metal")
|
||||
}
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.foregroundColor(.white.opacity(0.75))
|
||||
|
||||
// Position 3D de la 1ere personne (si detectee)
|
||||
if let first = poseListener.persons.values.first {
|
||||
Divider().background(Color.white.opacity(0.2))
|
||||
Text(String(format:
|
||||
"head (%.2f, %.2f) wristL (%.2f, %.2f) wristR (%.2f, %.2f)",
|
||||
first.head.x, first.head.y,
|
||||
first.wristL.x, first.wristL.y,
|
||||
first.wristR.x, first.wristR.y))
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.frame(maxWidth: 320, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.fill(Color.black.opacity(0.55))
|
||||
)
|
||||
.padding(12)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
|
||||
private func hudKey(_ key: String, _ desc: String) -> some View {
|
||||
HStack(spacing: 6) {
|
||||
Text(key)
|
||||
.frame(minWidth: 28, alignment: .leading)
|
||||
.foregroundColor(.pink)
|
||||
Text(desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user