diff --git a/.gitignore b/.gitignore index da2c31b..29dff36 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ UserSettings/ *.asmdef *.pdb *.mdb +!Samples~/**/*.cs +!Samples~/**/*.asmdef # OS/editor noise .DS_Store diff --git a/Editor/Aisu.SIM1.Face.Editor.dll b/Editor/Aisu.SIM1.Face.Editor.dll index 2362395..e2ef8c4 100644 Binary files a/Editor/Aisu.SIM1.Face.Editor.dll and b/Editor/Aisu.SIM1.Face.Editor.dll differ diff --git a/Runtime/Aisu.SIM1.Face.Runtime.dll b/Runtime/Aisu.SIM1.Face.Runtime.dll index 6374e8c..ff7671c 100644 Binary files a/Runtime/Aisu.SIM1.Face.Runtime.dll and b/Runtime/Aisu.SIM1.Face.Runtime.dll differ diff --git a/Samples~/Face Detection Demo/Aisu.SIM1.Face.Samples.FaceDetectionDemo.asmdef b/Samples~/Face Detection Demo/Aisu.SIM1.Face.Samples.FaceDetectionDemo.asmdef new file mode 100644 index 0000000..7786c90 --- /dev/null +++ b/Samples~/Face Detection Demo/Aisu.SIM1.Face.Samples.FaceDetectionDemo.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Aisu.SIM1.Face.Samples.FaceDetectionDemo", + "rootNamespace": "Aisu.SIM1.Face.Samples.FaceDetectionDemo", + "references": [ + "Aisu.SIM1.Face.Runtime" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Samples~/Face Detection Demo/Scripts/FaceDetectionDemoBootstrap.cs b/Samples~/Face Detection Demo/Scripts/FaceDetectionDemoBootstrap.cs new file mode 100644 index 0000000..af1ee47 --- /dev/null +++ b/Samples~/Face Detection Demo/Scripts/FaceDetectionDemoBootstrap.cs @@ -0,0 +1,382 @@ +using Aisu.SIM1.Face.Core; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +namespace Aisu.SIM1.Face.Samples.FaceDetectionDemo +{ + /// + /// 人脸检测 Demo 场景 UI 动态构建器。 + /// + public sealed class FaceDetectionDemoBootstrap : MonoBehaviour + { + private Font m_font; // UI 字体。 + + private void Awake() + { + Build(); + } + + /// + /// 构建 MVP 测试页面。 + /// + public void Build() + { + m_font = Resources.GetBuiltinResource("Arial.ttf"); + EnsureEventSystem(); + + var manager = FindObjectOfType() ?? new GameObject("FaceModuleManager").AddComponent(); + var camera = gameObject.AddComponent(); + var logger = gameObject.AddComponent(); + var userList = gameObject.AddComponent(); + var controller = gameObject.AddComponent(); + + var canvasGo = Create("FaceDetectionDemoCanvas", transform); + var canvas = canvasGo.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvasGo.AddComponent(); + var scaler = canvasGo.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1280, 720); + + var root = Create("Root", canvasGo.transform); + Stretch(root.GetComponent()); + var rootLayout = root.AddComponent(); + rootLayout.padding = new RectOffset(16, 16, 16, 16); + rootLayout.spacing = 10; + rootLayout.childControlWidth = true; + rootLayout.childForceExpandWidth = true; + + var title = Text(root.transform, "SI-M1 Face Detection Demo", 28, TextAnchor.MiddleLeft); + title.color = new Color(0.05f, 0.07f, 0.1f); + + var statusText = Text(root.transform, "摄像头未打开 | 模组未连接", 20, TextAnchor.MiddleLeft); + statusText.color = new Color(0.12f, 0.18f, 0.24f); + + var body = Create("Body", root.transform); + body.AddComponent().flexibleHeight = 1f; + var bodyLayout = body.AddComponent(); + bodyLayout.spacing = 12; + bodyLayout.childControlHeight = true; + bodyLayout.childForceExpandHeight = true; + + var left = Panel(body.transform, "LeftPanel", new Color(0.95f, 0.97f, 1f)); + left.AddComponent().flexibleWidth = 1f; + var leftLayout = left.AddComponent(); + leftLayout.padding = new RectOffset(10, 10, 10, 10); + leftLayout.spacing = 8; + leftLayout.childControlWidth = true; + leftLayout.childForceExpandWidth = true; + + var right = Panel(body.transform, "RightPanel", new Color(0.98f, 0.98f, 0.96f)); + right.AddComponent().preferredWidth = 430; + var rightLayout = right.AddComponent(); + rightLayout.padding = new RectOffset(10, 10, 10, 10); + rightLayout.spacing = 8; + rightLayout.childControlWidth = true; + rightLayout.childForceExpandWidth = true; + + var cameraDropdown = Dropdown(left.transform, "未发现摄像头"); + var cameraRow = Row(left.transform); + var refreshCameraButton = Button(cameraRow.transform, "刷新摄像头"); + var openCameraButton = Button(cameraRow.transform, "打开摄像头"); + var closeCameraButton = Button(cameraRow.transform, "关闭摄像头"); + + var previewFrame = Panel(left.transform, "CameraPreview", Color.black); + previewFrame.AddComponent().flexibleHeight = 1f; + var preview = Create("Preview", previewFrame.transform).AddComponent(); + preview.color = Color.white; + var previewRect = preview.GetComponent(); + previewRect.anchorMin = Vector2.zero; + previewRect.anchorMax = Vector2.one; + previewRect.offsetMin = Vector2.zero; + previewRect.offsetMax = Vector2.zero; + var aspect = preview.gameObject.AddComponent(); + aspect.aspectMode = AspectRatioFitter.AspectMode.FitInParent; + + Text(right.transform, "串口 / 模组", 22, TextAnchor.MiddleLeft); + var serialDropdown = Dropdown(right.transform, "手动输入串口"); + var serialInput = Input(right.transform, "串口名,例如 COM3 或 /dev/ttyUSB0"); + var baudInput = Input(right.transform, "115200"); + var serialRow = Row(right.transform); + var refreshSerialButton = Button(serialRow.transform, "刷新串口"); + var connectButton = Button(serialRow.transform, "连接模组"); + var disconnectButton = Button(serialRow.transform, "断开模组"); + + Text(right.transform, "人脸操作", 22, TextAnchor.MiddleLeft); + var userNameInput = Input(right.transform, "User"); + var adminToggle = Toggle(right.transform, "管理员", false); + var faceRow = Row(right.transform); + var enrollButton = Button(faceRow.transform, "录入人脸"); + var verifyButton = Button(faceRow.transform, "检测人脸"); + + var resultText = Text(right.transform, "结果: -", 20, TextAnchor.UpperLeft); + resultText.gameObject.AddComponent().preferredHeight = 58; + + Text(right.transform, "人脸数据", 22, TextAnchor.MiddleLeft); + var userButtonRow = Row(right.transform); + var refreshUsersButton = Button(userButtonRow.transform, "刷新用户列表"); + var userScroll = ScrollView(right.transform, out var userContent); + userScroll.GetComponent().preferredHeight = 170; + + Text(root.transform, "操作日志", 22, TextAnchor.MiddleLeft); + var logRow = Row(root.transform); + var clearLogButton = Button(logRow.transform, "清空日志"); + var logScroll = ScrollView(root.transform, out var logContent); + logScroll.GetComponent().preferredHeight = 150; + var logText = Text(logContent, string.Empty, 18, TextAnchor.UpperLeft); + logText.font = m_font; + logText.color = Color.white; + logText.horizontalOverflow = HorizontalWrapMode.Wrap; + logText.verticalOverflow = VerticalWrapMode.Overflow; + logText.gameObject.AddComponent().flexibleWidth = 1f; + logContent.gameObject.GetComponent().color = new Color(0.07f, 0.08f, 0.09f); + + camera.Bind(preview, aspect, logger); + logger.Bind(logText, logScroll); + userList.Bind(userContent, m_font, controller.DeleteUser); + controller.Bind(manager, camera, logger, userList, cameraDropdown, serialDropdown, serialInput, baudInput, userNameInput, adminToggle, statusText, resultText); + + refreshCameraButton.onClick.AddListener(controller.RefreshCameras); + openCameraButton.onClick.AddListener(controller.OpenCamera); + closeCameraButton.onClick.AddListener(controller.CloseCamera); + refreshSerialButton.onClick.AddListener(controller.RefreshSerialPorts); + connectButton.onClick.AddListener(controller.ConnectModule); + disconnectButton.onClick.AddListener(controller.DisconnectModule); + enrollButton.onClick.AddListener(controller.EnrollFace); + verifyButton.onClick.AddListener(controller.VerifyFace); + refreshUsersButton.onClick.AddListener(controller.RefreshUsers); + clearLogButton.onClick.AddListener(logger.Clear); + } + + private static void EnsureEventSystem() + { + if (FindObjectOfType() != null) + { + return; + } + + var eventSystem = new GameObject("EventSystem"); + eventSystem.AddComponent(); + eventSystem.AddComponent(); + } + + private GameObject Panel(Transform parent, string name, Color color) + { + var go = Create(name, parent); + go.AddComponent().color = color; + return go; + } + + private GameObject Row(Transform parent) + { + var row = Create("Row", parent); + var layout = row.AddComponent(); + layout.spacing = 8; + layout.childControlWidth = true; + layout.childForceExpandWidth = true; + row.AddComponent().preferredHeight = 44; + return row; + } + + private Button Button(Transform parent, string text) + { + var go = Create(text, parent); + go.AddComponent().color = new Color(0.78f, 0.86f, 0.95f); + var button = go.AddComponent