第2章 构建AR世界的数学基石:空间计算与交互核心

2.1 从现实到虚拟:空间映射的数学原理

增强现实(AR)的本质在于将虚拟信息精准地叠加到现实物理空间,这一过程离不开数学的支撑。在商业AR应用开发中,无论是产品可视化、室内导航还是工业维修指导,都需要开发者深刻理解三维空间中的数学概念。本章将深入探讨AR开发中不可或缺的数学基础,并通过完整的商业项目实例,展示如何将这些理论转化为实用的代码。

任何AR场景都是一个三维坐标系系统。Unity引擎采用左手坐标系,即X轴向右,Y轴向上,Z轴向前。理解这个坐标系是AR开发的第一步。当我们通过手机摄像头观察世界时,设备需要实时计算自身在物理空间中的位置(位置向量)和朝向(旋转),这个计算过程本质上就是求解一个变换矩阵。

在商业AR应用中,比如家具陈列系统,我们需要将虚拟沙发准确地放在用户指定的地板位置。这涉及到多个坐标系的转换:从模型自身的局部坐标系,到场景中的世界坐标系,最终通过摄像头投影到二维的屏幕坐标系。这个转换过程可以用一个4x4的变换矩阵来表示:

M = T * R * S

其中T是平移矩阵,R是旋转矩阵,S是缩放矩阵。在Unity中,每个GameObject的Transform组件都隐含着这个变换矩阵。

让我们通过一个商业实例来理解这个过程。假设我们正在开发一个AR商品展示应用,用户可以在自己的房间里查看不同尺寸的虚拟书架。

using UnityEngine;

namespace ARCommercialDemo.MathFoundation
{
    // 商品空间定位管理器 - 处理商品在AR空间中的放置与变换
    public class ProductPlacementManager : MonoBehaviour
    {
        // 商品实例的引用
        private GameObject currentProductInstance;
        
        // 原始商品尺寸(从模型获取)
        private Vector3 originalProductSize;
        
        // 目标放置位置(从平面检测获取)
        private Vector3 targetWorldPosition;
        
        // 目标旋转角度(通常与检测到的平面对齐)
        private Quaternion targetWorldRotation;
        
        // 初始化商品实例
        public void InitializeProduct(GameObject productPrefab, Vector3 detectedPosition)
        {
            // 销毁之前的实例
            if (currentProductInstance != null)
            {
                Destroy(currentProductInstance);
            }
            
            // 实例化新商品
            currentProductInstance = Instantiate(productPrefab);
            
            // 获取原始尺寸
            originalProductSize = GetProductOriginalSize(productPrefab);
            
            // 设置初始位置(使用检测到的平面位置)
            targetWorldPosition = detectedPosition;
            
            // 默认旋转:与平面对齐,正面朝向相机
            Vector3 cameraForward = Camera.main.transform.forward;
            cameraForward.y = 0; // 保持水平
            targetWorldRotation = Quaternion.LookRotation(cameraForward.normalized);
            
            ApplyTransformation();
        }
        
        // 获取商品的原始尺寸(从渲染边界计算)
        private Vector3 GetProductOriginalSize(GameObject product)
        {
            Renderer productRenderer = product.GetComponent<Renderer>();
            if (productRenderer != null)
            {
                return productRenderer.bounds.size;
            }
            
            // 如果商品没有Renderer,尝试从子物体获取
            Renderer[] childRenderers = product.GetComponentsInChildren<Renderer>();
            if (childRenderers.Length > 0)
            {
                Bounds combinedBounds = childRenderers[0].bounds;
                for (int i = 1; i < childRenderers.Length; i++)
                {
                    combinedBounds.Encapsulate(childRenderers[i].bounds);
                }
                return combinedBounds.size;
            }
            
            // 默认尺寸
            return Vector3.one;
        }
        
        // 应用变换到商品实例
        private void ApplyTransformation()
        {
            if (currentProductInstance == null)
            {
                return;
            }
            
            Transform productTransform = currentProductInstance.transform;
            
            // 应用位置
            productTransform.position = targetWorldPosition;
            
            // 应用旋转
            productTransform.rotation = targetWorldRotation;
            
            // 保持原始比例
            productTransform.localScale = Vector3.one;
        }
        
        // 更新商品位置(响应AR平面更新)
        public void UpdateProductPosition(Vector3 newPosition, Quaternion newRotation)
        {
            targetWorldPosition = newPosition;
            targetWorldRotation = newRotation;
            ApplyTransformation();
        }
        
        // 调整商品尺寸(商业应用中常见的功能)
        public void ScaleProduct(float scaleFactor)
        {
            if (currentProductInstance != null)
            {
                // 限制缩放范围(商业需求:通常不允许无限制缩放)
                float minScale = 0.1f;
                float maxScale = 5.0f;
                scaleFactor = Mathf.Clamp(scaleFactor, minScale, maxScale);
                
                currentProductInstance.transform.localScale = Vector3.one * scaleFactor;
            }
        }
        
        // 计算商品是否适合目标空间(商业逻辑)
        public bool CheckProductFitsSpace(Vector3 availableSpaceSize)
        {
            if (currentProductInstance == null)
            {
                return false;
            }
            
            // 获取当前商品的实际尺寸(考虑缩放)
            Vector3 currentSize = GetCurrentProductSize();
            
            // 检查每个维度是否适合
            bool fitsWidth = currentSize.x <= availableSpaceSize.x;
            bool fitsHeight = currentSize.y <= availableSpaceSize.y;
            bool fitsDepth = currentSize.z <= availableSpaceSize.z;
            
            // 商业逻辑:记录分析数据
            LogFittingAnalysis(currentSize, availableSpaceSize, fitsWidth && fitsHeight && fitsDepth);
            
            return fitsWidth && fitsHeight && fitsDepth;
        }
        
        // 获取当前商品的实际尺寸
        private Vector3 GetCurrentProductSize()
        {
            Vector3 scaledSize = originalProductSize;
            if (currentProductInstance != null)
            {
                scaledSize = Vector3.Scale(originalProductSize, currentProductInstance.transform.lossyScale);
            }
            return scaledSize;
        }
        
        // 记录商品适配分析数据(用于商业分析)
        private void LogFittingAnalysis(Vector3 productSize, Vector3 spaceSize, bool fits)
        {
            Debug.Log($"商品适配分析 - 产品尺寸: {productSize}, " +
                     $"空间尺寸: {spaceSize}, " +
                     $"适配结果: {(fits ? "适合" : "不适合")}");
            
            // 在实际商业项目中,这里通常会发送数据到分析服务器
            // SendAnalyticsData("product_fit_check", new { productSize, spaceSize, fits });
        }
    }
}

2.2 向量数学在AR交互中的应用

向量是AR开发中最基本的数学工具。一个三维向量可以表示位置、方向或速度。在商业AR应用中,向量的运算无处不在。

**点积(Dot Product)**在AR中常用于判断朝向关系。例如,在零售AR应用中,我们需要确保虚拟商品标签始终面向用户,这时就需要用到点积来计算相机与标签之间的角度。

**叉积(Cross Product)**则用于计算法向量和旋转轴。在AR家具布置应用中,当用户旋转虚拟家具时,我们需要计算旋转轴,这就要用到叉积运算。

让我们通过一个完整的商业实例来展示向量数学的实际应用。假设我们正在开发一个AR汽车展示应用,用户可以通过手势旋转查看汽车的不同角度。

using UnityEngine;

namespace ARCommercialDemo.VectorMathematics
{
    // AR汽车展示交互控制器
    public class ARCarViewerController : MonoBehaviour
    {
        // 汽车模型引用
        public GameObject carModel;
        
        // 旋转速度控制
        public float rotationSpeed = 2.0f;
        
        // 自动旋转参数
        public bool enableAutoRotation = true;
        public float autoRotationSpeed = 0.5f;
        
        // 触摸/鼠标交互参数
        private Vector2 previousTouchPosition;
        private bool isDragging = false;
        
        // 目标旋转角度(用于平滑旋转)
        private Quaternion targetRotation;
        
        // 旋转限制(商业需求:控制旋转范围)
        public float minXRotation = -30f;
        public float maxXRotation = 30f;
        
        // 初始化
        private void Start()
        {
            if (carModel == null)
            {
                Debug.LogError("汽车模型未分配!");
                return;
            }
            
            targetRotation = carModel.transform.rotation;
            
            // 初始化汽车位置,确保在摄像机视野内
            InitializeCarPosition();
        }
        
        // 初始化汽车位置
        private void InitializeCarPosition()
        {
            // 将汽车放置在摄像机前方适当位置
            Camera mainCamera = Camera.main;
            if (mainCamera != null)
            {
                Vector3 cameraForward = mainCamera.transform.forward;
                cameraForward.y = 0; // 保持水平
                
                // 计算放置位置(摄像机前方3米)
                Vector3 placementPosition = mainCamera.transform.position + cameraForward.normalized * 3f;
                placementPosition.y = 0; // 放置在地面高度
                
                carModel.transform.position = placementPosition;
                
                // 使汽车面向摄像机
                Vector3 lookDirection = mainCamera.transform.position - placementPosition;
                lookDirection.y = 0;
                if (lookDirection != Vector3.zero)
                {
                    carModel.transform.rotation = Quaternion.LookRotation(lookDirection.normalized);
                    targetRotation = carModel.transform.rotation;
                }
            }
        }
        
        // 每帧更新
        private void Update()
        {
            if (carModel == null)
            {
                return;
            }
            
            // 处理用户输入
            HandleUserInput();
            
            // 应用自动旋转(如果启用)
            if (enableAutoRotation && !isDragging)
            {
                ApplyAutoRotation();
            }
            
            // 平滑旋转到目标角度
            ApplySmoothRotation();
        }
        
        // 处理用户输入(触摸/鼠标)
        private void HandleUserInput()
        {
            // 鼠标输入(用于编辑器测试)
            HandleMouseInput();
            
            // 触摸输入(移动设备)
            HandleTouchInput();
        }
        
        // 处理鼠标输入
        private void HandleMouseInput()
        {
            if (Input.GetMouseButtonDown(0))
            {
                // 开始拖拽
                isDragging = true;
                previousTouchPosition = Input.mousePosition;
                
                // 商业逻辑:记录用户开始交互
                LogUserInteraction("mouse_drag_start");
            }
            else if (Input.GetMouseButtonUp(0))
            {
                // 结束拖拽
                isDragging = false;
                
                // 商业逻辑:记录用户结束交互
                LogUserInteraction("mouse_drag_end");
            }
            
            if (isDragging && Input.GetMouseButton(0))
            {
                // 计算鼠标移动量
                Vector2 currentMousePosition = Input.mousePosition;
                Vector2 deltaPosition = currentMousePosition - previousTouchPosition;
                
                // 应用旋转
                ApplyManualRotation(deltaPosition);
                
                previousTouchPosition = currentMousePosition;
            }
        }
        
        // 处理触摸输入
        private void HandleTouchInput()
        {
            if (Input.touchCount == 1)
            {
                Touch touch = Input.GetTouch(0);
                
                switch (touch.phase)
                {
                    case TouchPhase.Began:
                        isDragging = true;
                        previousTouchPosition = touch.position;
                        LogUserInteraction("touch_drag_start");
                        break;
                        
                    case TouchPhase.Moved:
                        if (isDragging)
                        {
                            Vector2 deltaPosition = touch.position - previousTouchPosition;
                            ApplyManualRotation(deltaPosition);
                            previousTouchPosition = touch.position;
                        }
                        break;
                        
                    case TouchPhase.Ended:
                    case TouchPhase.Canceled:
                        isDragging = false;
                        LogUserInteraction("touch_drag_end");
                        break;
                }
            }
            else if (Input.touchCount == 0)
            {
                isDragging = false;
            }
        }
        
        // 应用手动旋转(基于向量运算)
        private void ApplyManualRotation(Vector2 deltaPosition)
        {
            // 计算旋转量
            float rotationX = deltaPosition.y * rotationSpeed * Time.deltaTime;
            float rotationY = -deltaPosition.x * rotationSpeed * Time.deltaTime;
            
            // 使用叉积计算旋转轴(向量数学的应用)
            // 这里我们使用简单的欧拉角,但理解叉积对于更复杂的旋转很重要
            Vector3 currentEuler = targetRotation.eulerAngles;
            
            // 应用旋转(限制X轴旋转)
            float newXRotation = currentEuler.x + rotationX;
            newXRotation = ClampAngle(newXRotation, minXRotation, maxXRotation);
            
            Vector3 newEuler = new Vector3(
                newXRotation,
                currentEuler.y + rotationY,
                currentEuler.z
            );
            
            // 使用四元数避免万向节锁
            targetRotation = Quaternion.Euler(newEuler);
        }
        
        // 应用自动旋转
        private void ApplyAutoRotation()
        {
            // 围绕Y轴缓慢旋转
            float autoRotationAmount = autoRotationSpeed * Time.deltaTime;
            targetRotation *= Quaternion.Euler(0, autoRotationAmount, 0);
        }
        
        // 应用平滑旋转
        private void ApplySmoothRotation()
        {
            // 使用球面线性插值实现平滑旋转
            carModel.transform.rotation = Quaternion.Slerp(
                carModel.transform.rotation,
                targetRotation,
                5.0f * Time.deltaTime
            );
        }
        
        // 角度钳制函数(处理欧拉角超过360度的情况)
        private float ClampAngle(float angle, float min, float max)
        {
            if (angle < -360f)
            {
                angle += 360f;
            }
            if (angle > 360f)
            {
                angle -= 360f;
            }
            
            return Mathf.Clamp(angle, min, max);
        }
        
        // 重置汽车旋转到初始状态
        public void ResetCarRotation()
        {
            // 商业逻辑:提供重置功能,提升用户体验
            Camera mainCamera = Camera.main;
            if (mainCamera != null)
            {
                Vector3 lookDirection = mainCamera.transform.position - carModel.transform.position;
                lookDirection.y = 0;
                if (lookDirection != Vector3.zero)
                {
                    targetRotation = Quaternion.LookRotation(lookDirection.normalized);
                }
            }
            
            LogUserInteraction("rotation_reset");
        }
        
        // 记录用户交互(商业分析)
        private void LogUserInteraction(string interactionType)
        {
            Debug.Log($"用户交互记录: {interactionType}, 时间: {Time.time}");
            
            // 在实际商业项目中,这里会收集用户交互数据用于分析
            // 例如:用户最喜欢查看汽车的哪个角度,平均查看时间等
            
            // SendAnalyticsData("user_interaction", new { 
            //     type = interactionType, 
            //     timestamp = Time.time,
            //     carModel = carModel.name
            // });
        }
        
        // 计算汽车与摄像机的相对方向(向量点积的应用)
        public float GetCarCameraAlignment()
        {
            if (carModel == null || Camera.main == null)
            {
                return 0f;
            }
            
            // 获取汽车前向向量
            Vector3 carForward = carModel.transform.forward;
            
            // 获取摄像机看向汽车的向量
            Vector3 cameraToCar = (carModel.transform.position - Camera.main.transform.position).normalized;
            
            // 使用点积计算对齐程度
            // 点积结果范围:-1(完全相反)到1(完全对齐)
            float alignment = Vector3.Dot(carForward, cameraToCar);
            
            return alignment;
        }
        
        // 获取当前视图的描述(用于语音提示或UI显示)
        public string GetCurrentViewDescription()
        {
            float alignment = GetCarCameraAlignment();
            
            if (alignment > 0.7f)
            {
                return "您正在查看汽车前部";
            }
            else if (alignment < -0.7f)
            {
                return "您正在查看汽车后部";
            }
            else if (Mathf.Abs(alignment) < 0.3f)
            {
                // 使用叉积判断是左侧还是右侧
                Vector3 carForward = carModel.transform.forward;
                Vector3 cameraToCar = (carModel.transform.position - Camera.main.transform.position).normalized;
                Vector3 crossResult = Vector3.Cross(carForward, cameraToCar);
                
                if (crossResult.y > 0)
                {
                    return "您正在查看汽车右侧";
                }
                else
                {
                    return "您正在查看汽车左侧";
                }
            }
            
            return "您正在查看汽车角度";
        }
    }
}

2.3 四元数:AR旋转的数学表达

在三维旋转中,欧拉角虽然直观,但存在万向节锁问题。四元数(Quaternion)通过四个数值(x, y, z, w)表示旋转,避免了这个问题,成为Unity中表示旋转的标准方式。

四元数的数学原理较为复杂,但开发者只需要理解其基本性质:

  1. 单位四元数表示旋转(长度为1)
  2. 四元数乘法表示旋转的组合
  3. 球面线性插值(Slerp)可以实现平滑旋转过渡

在商业AR导航应用中,当用户改变方向时,虚拟导航箭头需要平滑地旋转指向新方向,这正是四元数的典型应用场景。

using UnityEngine;

namespace ARCommercialDemo.QuaternionApplications
{
    // AR导航箭头控制器
    public class ARNavigationArrow : MonoBehaviour
    {
        // 导航箭头模型
        public GameObject arrowModel;
        
        // 当前目标位置
        private Vector3 targetPosition;
        
        // 旋转平滑度
        public float rotationSmoothness = 5.0f;
        
        // 箭头浮动效果参数
        public float floatAmplitude = 0.2f;
        public float floatFrequency = 1.0f;
        
        private Vector3 initialArrowPosition;
        private float floatTimer = 0f;
        
        // 导航状态
        private bool isNavigating = false;
        
        // 初始化
        private void Start()
        {
            if (arrowModel == null)
            {
                Debug.LogError("导航箭头模型未分配!");
                return;
            }
            
            initialArrowPosition = arrowModel.transform.localPosition;
            
            // 初始隐藏箭头
            SetArrowVisibility(false);
        }
        
        // 每帧更新
        private void Update()
        {
            if (!isNavigating || arrowModel == null)
            {
                return;
            }
            
            // 更新箭头旋转(指向目标)
            UpdateArrowRotation();
            
            // 应用浮动动画
            ApplyFloatingAnimation();
            
            // 更新箭头位置(保持在摄像机视野内)
            UpdateArrowPosition();
        }
        
        // 开始导航到目标
        public void StartNavigation(Vector3 destination)
        {
            targetPosition = destination;
            isNavigating = true;
            
            SetArrowVisibility(true);
            
            Debug.Log($"开始导航到目标位置: {destination}");
            
            // 商业逻辑:记录导航开始
            LogNavigationEvent("navigation_started", transform.position, destination);
        }
        
        // 停止导航
        public void StopNavigation()
        {
            isNavigating = false;
            SetArrowVisibility(false);
            
            Debug.Log("导航已停止");
            
            // 商业逻辑:记录导航结束
            LogNavigationEvent("navigation_stopped", transform.position, targetPosition);
        }
        
        // 更新箭头旋转(使用四元数)
        private void UpdateArrowRotation()
        {
            if (Camera.main == null)
            {
                return;
            }
            
            // 计算从箭头到目标的方向
            Vector3 directionToTarget = targetPosition - arrowModel.transform.position;
            
            // 忽略Y轴差异(假设在同一水平面)
            directionToTarget.y = 0;
            
            // 如果方向为零向量,则不旋转
            if (directionToTarget == Vector3.zero)
            {
                return;
            }
            
            // 使用四元数创建目标旋转
            Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized);
            
            // 考虑摄像机旋转,使箭头始终面向用户
            Quaternion cameraRotation = Camera.main.transform.rotation;
            
            // 组合旋转:首先指向目标,然后调整以面向摄像机
            Quaternion finalRotation = targetRotation * Quaternion.Inverse(cameraRotation) * Quaternion.Euler(0, 180, 0);
            
            // 使用四元数球面线性插值实现平滑旋转
            arrowModel.transform.rotation = Quaternion.Slerp(
                arrowModel.transform.rotation,
                finalRotation,
                rotationSmoothness * Time.deltaTime
            );
        }
        
        // 应用浮动动画
        private void ApplyFloatingAnimation()
        {
            floatTimer += Time.deltaTime;
            
            // 计算浮动偏移
            float floatOffset = Mathf.Sin(floatTimer * floatFrequency * Mathf.PI * 2f) * floatAmplitude;
            
            // 应用浮动效果
            Vector3 newPosition = initialArrowPosition;
            newPosition.y += floatOffset;
            arrowModel.transform.localPosition = newPosition;
        }
        
        // 更新箭头位置(保持在摄像机视野内)
        private void UpdateArrowPosition()
        {
            if (Camera.main == null)
            {
                return;
            }
            
            // 将箭头放置在摄像机前方固定距离
            float distanceFromCamera = 2.0f;
            
            // 获取摄像机的前向方向(水平方向)
            Vector3 cameraForward = Camera.main.transform.forward;
            cameraForward.y = 0;
            cameraForward.Normalize();
            
            // 计算箭头位置
            Vector3 arrowWorldPosition = Camera.main.transform.position + cameraForward * distanceFromCamera;
            
            // 设置箭头高度(略低于摄像机)
            arrowWorldPosition.y = Camera.main.transform.position.y - 0.5f;
            
            // 更新箭头位置
            transform.position = arrowWorldPosition;
        }
        
        // 设置箭头可见性
        private void SetArrowVisibility(bool isVisible)
        {
            if (arrowModel != null)
            {
                arrowModel.SetActive(isVisible);
            }
        }
        
        // 检查是否到达目标
        public bool CheckIfDestinationReached(float arrivalThreshold = 1.0f)
        {
            if (!isNavigating)
            {
                return false;
            }
            
            // 计算到目标的水平距离
            Vector3 currentPosition = transform.position;
            currentPosition.y = 0;
            Vector3 targetPos = targetPosition;
            targetPos.y = 0;
            
            float distanceToTarget = Vector3.Distance(currentPosition, targetPos);
            
            bool reached = distanceToTarget <= arrivalThreshold;
            
            if (reached)
            {
                Debug.Log($"已到达目的地,距离: {distanceToTarget}");
                
                // 商业逻辑:记录到达事件
                LogNavigationEvent("destination_reached", currentPosition, targetPosition);
            }
            
            return reached;
        }
        
        // 计算导航进度(0到1之间)
        public float CalculateNavigationProgress(Vector3 startPosition)
        {
            if (!isNavigating)
            {
                return 0f;
            }
            
            // 计算总距离
            Vector3 startPos = startPosition;
            startPos.y = 0;
            Vector3 targetPos = targetPosition;
            targetPos.y = 0;
            float totalDistance = Vector3.Distance(startPos, targetPos);
            
            if (totalDistance <= 0.001f)
            {
                return 1f;
            }
            
            // 计算当前距离
            Vector3 currentPos = transform.position;
            currentPos.y = 0;
            float currentDistance = Vector3.Distance(currentPos, targetPos);
            
            // 计算进度
            float progress = 1f - (currentDistance / totalDistance);
            
            // 限制在0到1之间
            progress = Mathf.Clamp01(progress);
            
            return progress;
        }
        
        // 计算到达时间估计(简单版本)
        public float EstimateTimeToDestination(float averageSpeed = 1.4f)
        {
            if (!isNavigating)
            {
                return 0f;
            }
            
            // 计算剩余距离
            Vector3 currentPos = transform.position;
            currentPos.y = 0;
            Vector3 targetPos = targetPosition;
            targetPos.y = 0;
            
            float remainingDistance = Vector3.Distance(currentPos, targetPos);
            
            // 计算估计时间(秒)
            float estimatedTime = remainingDistance / averageSpeed;
            
            return estimatedTime;
        }
        
        // 获取导航方向提示
        public string GetDirectionHint()
        {
            if (!isNavigating || Camera.main == null)
            {
                return "未在导航中";
            }
            
            // 计算目标方向
            Vector3 directionToTarget = targetPosition - transform.position;
            directionToTarget.y = 0;
            
            if (directionToTarget == Vector3.zero)
            {
                return "您已到达目的地";
            }
            
            // 获取摄像机前向方向
            Vector3 cameraForward = Camera.main.transform.forward;
            cameraForward.y = 0;
            cameraForward.Normalize();
            
            // 计算方向向量之间的角度
            float angle = Vector3.SignedAngle(cameraForward, directionToTarget, Vector3.up);
            
            // 根据角度提供方向提示
            if (Mathf.Abs(angle) < 30f)
            {
                return "目标在您正前方";
            }
            else if (angle > 30f && angle < 150f)
            {
                return "目标在您右侧";
            }
            else if (angle < -30f && angle > -150f)
            {
                return "目标在您左侧";
            }
            else
            {
                return "目标在您后方";
            }
        }
        
        // 记录导航事件(商业分析)
        private void LogNavigationEvent(string eventType, Vector3 currentPosition, Vector3 targetPosition)
        {
            Debug.Log($"导航事件: {eventType}, " +
                     $"当前位置: {currentPosition}, " +
                     $"目标位置: {targetPosition}, " +
                     $"时间: {Time.time}");
            
            // 在实际商业项目中,这里会收集导航数据用于分析
            // 例如:用户平均导航距离,常见目的地,导航成功率等
            
            // SendAnalyticsData("navigation_event", new { 
            //     event_type = eventType,
            //     current_position = currentPosition,
            //     target_position = targetPosition,
            //     timestamp = Time.time
            // });
        }
    }
}

2.4 射线检测:AR交互的数学实现

射线检测是AR交互的核心技术之一。当用户在屏幕上点击时,我们需要从摄像机位置发射一条射线,检测它与虚拟物体或现实平面的交点。这本质上是一个数学上的直线与几何体的相交检测问题。

在商业AR家具布置应用中,用户通过点击屏幕来选择放置家具的位置。这个过程涉及:

  1. 将屏幕坐标转换为世界空间中的射线
  2. 检测射线与AR平面的交点
  3. 在交点位置实例化家具模型

让我们通过一个完整的商业实例来展示射线检测的实现:

using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

namespace ARCommercialDemo.RaycastImplementation
{
    // AR家具布置管理器
    public class ARFurniturePlacer : MonoBehaviour
    {
        // AR射线管理器
        public ARRaycastManager arRaycastManager;
        
        // 家具预制体
        public GameObject[] furniturePrefabs;
        
        // 当前选中的家具索引
        private int selectedFurnitureIndex = 0;
        
        // 当前放置的家具实例
        private GameObject currentFurnitureInstance;
        
        // 放置指示器(显示将要放置的位置)
        public GameObject placementIndicator;
        
        // 放置有效性反馈材料
        public Material validPlacementMaterial;
        public Material invalidPlacementMaterial;
        
        // 当前放置位置是否有效
        private bool isPlacementValid = false;
        
        // 当前放置位置
        private Pose placementPose;
        
        // 平面检测层
        private LayerMask planeLayerMask;
        
        // 初始化
        private void Start()
        {
            if (arRaycastManager == null)
            {
                arRaycastManager = FindObjectOfType<ARRaycastManager>();
            }
            
            if (placementIndicator != null)
            {
                placementIndicator.SetActive(false);
            }
            
            // 设置平面层掩码
            planeLayerMask = 1 << LayerMask.NameToLayer("ARPlane");
            
            Debug.Log("AR家具布置系统已初始化");
        }
        
        // 每帧更新
        private void Update()
        {
            if (arRaycastManager == null)
            {
                return;
            }
            
            // 更新放置指示器
            UpdatePlacementIndicator();
            
            // 处理用户输入
            HandleUserInput();
        }
        
        // 更新放置指示器
        private void UpdatePlacementIndicator()
        {
            if (placementIndicator == null)
            {
                return;
            }
            
            // 从屏幕中心发射射线
            Vector2 screenCenter = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f);
            
            // 执行AR射线检测
            var hits = new System.Collections.Generic.List<ARRaycastHit>();
            
            if (arRaycastManager.Raycast(screenCenter, hits, TrackableType.PlaneWithinPolygon))
            {
                // 找到有效的AR平面
                isPlacementValid = true;
                placementPose = hits[0].pose;
                
                // 获取平面法线
                Vector3 planeNormal = hits[0].pose.up;
                
                // 调整放置姿态,使家具与平面对齐
                placementPose.rotation = Quaternion.FromToRotation(Vector3.up, planeNormal);
                
                // 显示并更新指示器位置
                placementIndicator.SetActive(true);
                placementIndicator.transform.SetPositionAndRotation(
                    placementPose.position, 
                    placementPose.rotation
                );
                
                // 更新指示器外观(根据放置有效性)
                UpdateIndicatorAppearance(true);
            }
            else
            {
                // 没有检测到有效平面
                isPlacementValid = false;
                placementIndicator.SetActive(false);
            }
        }
        
        // 更新指示器外观
        private void UpdateIndicatorAppearance(bool isValid)
        {
            if (placementIndicator == null || validPlacementMaterial == null || invalidPlacementMaterial == null)
            {
                return;
            }
            
            Renderer indicatorRenderer = placementIndicator.GetComponent<Renderer>();
            if (indicatorRenderer != null)
            {
                indicatorRenderer.material = isValid ? validPlacementMaterial : invalidPlacementMaterial;
            }
        }
        
        // 处理用户输入
        private void HandleUserInput()
        {
            // 处理触摸输入
            if (Input.touchCount > 0)
            {
                Touch touch = Input.GetTouch(0);
                
                if (touch.phase == TouchPhase.Began)
                {
                    // 检测是否点击了UI元素
                    if (IsPointerOverUIObject(touch.position))
                    {
                        return;
                    }
                    
                    if (isPlacementValid)
                    {
                        // 放置家具
                        PlaceFurniture();
                    }
                    else
                    {
                        // 尝试从触摸位置发射射线
                        TryRaycastFromTouch(touch.position);
                    }
                }
            }
            
            // 处理鼠标输入(编辑器测试)
            if (Input.GetMouseButtonDown(0))
            {
                // 检测是否点击了UI元素
                if (IsPointerOverUIObject(Input.mousePosition))
                {
                    return;
                }
                
                if (isPlacementValid)
                {
                    PlaceFurniture();
                }
                else
                {
                    TryRaycastFromTouch(Input.mousePosition);
                }
            }
        }
        
        // 尝试从触摸位置发射射线
        private void TryRaycastFromTouch(Vector2 touchPosition)
        {
            // 执行射线检测
            Ray ray = Camera.main.ScreenPointToRay(touchPosition);
            RaycastHit hit;
            
            // 检测与现有家具的交互
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                GameObject hitObject = hit.collider.gameObject;
                
                // 检查是否击中了已放置的家具
                if (hitObject.CompareTag("Furniture"))
                {
                    // 选中家具进行后续操作(移动、旋转、删除等)
                    SelectFurniture(hitObject);
                }
            }
        }
        
        // 放置家具
        private void PlaceFurniture()
        {
            if (furniturePrefabs == null || furniturePrefabs.Length == 0)
            {
                Debug.LogWarning("没有可放置的家具预制体");
                return;
            }
            
            if (selectedFurnitureIndex < 0 || selectedFurnitureIndex >= furniturePrefabs.Length)
            {
                selectedFurnitureIndex = 0;
            }
            
            // 销毁之前放置的家具(如果存在)
            if (currentFurnitureInstance != null)
            {
                Destroy(currentFurnitureInstance);
            }
            
            // 实例化新家具
            GameObject furniturePrefab = furniturePrefabs[selectedFurnitureIndex];
            currentFurnitureInstance = Instantiate(
                furniturePrefab, 
                placementPose.position, 
                placementPose.rotation
            );
            
            // 设置家具标签(用于后续识别)
            currentFurnitureInstance.tag = "Furniture";
            
            // 添加家具交互组件
            AddFurnitureInteraction(currentFurnitureInstance);
            
            Debug.Log($"家具已放置: {furniturePrefab.name}, 位置: {placementPose.position}");
            
            // 商业逻辑:记录家具放置事件
            LogFurniturePlacement(furniturePrefab.name, placementPose.position);
        }
        
        // 添加家具交互组件
        private void AddFurnitureInteraction(GameObject furniture)
        {
            // 添加碰撞器(如果不存在)
            if (furniture.GetComponent<Collider>() == null)
            {
                // 尝试从子物体获取渲染器来计算碰撞器大小
                Renderer renderer = furniture.GetComponentInChildren<Renderer>();
                if (renderer != null)
                {
                    BoxCollider collider = furniture.AddComponent<BoxCollider>();
                    collider.size = renderer.bounds.size;
                    collider.center = renderer.bounds.center - furniture.transform.position;
                }
                else
                {
                    furniture.AddComponent<BoxCollider>();
                }
            }
            
            // 添加家具控制器脚本
            FurnitureController furnitureController = furniture.AddComponent<FurnitureController>();
            furnitureController.Initialize(this);
        }
        
        // 选择家具
        private void SelectFurniture(GameObject furniture)
        {
            Debug.Log($"家具被选中: {furniture.name}");
            
            // 高亮显示选中的家具
            HighlightFurniture(furniture, true);
            
            // 商业逻辑:记录家具选择事件
            LogFurnitureSelection(furniture.name);
        }
        
        // 高亮显示家具
        private void HighlightFurniture(GameObject furniture, bool highlight)
        {
            Renderer[] renderers = furniture.GetComponentsInChildren<Renderer>();
            
            foreach (Renderer renderer in renderers)
            {
                Material[] materials = renderer.materials;
                
                for (int i = 0; i < materials.Length; i++)
                {
                    if (highlight)
                    {
                        // 创建高亮材质
                        Material highlightMaterial = new Material(materials[i]);
                        highlightMaterial.SetFloat("_Metallic", 0.8f);
                        highlightMaterial.SetFloat("_Glossiness", 1.0f);
                        materials[i] = highlightMaterial;
                    }
                    else
                    {
                        // 恢复原始材质(在实际项目中应保存原始材质)
                    }
                }
                
                renderer.materials = materials;
            }
        }
        
        // 选择家具类型
        public void SelectFurnitureType(int index)
        {
            if (index >= 0 && index < furniturePrefabs.Length)
            {
                selectedFurnitureIndex = index;
                Debug.Log($"已选择家具类型: {furniturePrefabs[index].name}");
            }
        }
        
        // 旋转当前家具
        public void RotateCurrentFurniture(float angle)
        {
            if (currentFurnitureInstance != null)
            {
                currentFurnitureInstance.transform.Rotate(Vector3.up, angle, Space.World);
                
                // 商业逻辑:记录家具旋转
                LogFurnitureRotation(angle);
            }
        }
        
        // 调整当前家具大小
        public void ScaleCurrentFurniture(float scaleFactor)
        {
            if (currentFurnitureInstance != null)
            {
                Vector3 currentScale = currentFurnitureInstance.transform.localScale;
                currentFurnitureInstance.transform.localScale = currentScale * scaleFactor;
                
                // 商业逻辑:记录家具缩放
                LogFurnitureScaling(scaleFactor);
            }
        }
        
        // 删除当前家具
        public void DeleteCurrentFurniture()
        {
            if (currentFurnitureInstance != null)
            {
                string furnitureName = currentFurnitureInstance.name;
                Destroy(currentFurnitureInstance);
                currentFurnitureInstance = null;
                
                Debug.Log($"家具已删除: {furnitureName}");
                
                // 商业逻辑:记录家具删除
                LogFurnitureDeletion(furnitureName);
            }
        }
        
        // 检查指针是否在UI对象上
        private bool IsPointerOverUIObject(Vector2 position)
        {
            // 在实际项目中,这里会实现具体的UI检测逻辑
            // 可以使用EventSystem.current.IsPointerOverGameObject()等方法
            return false;
        }
        
        // 记录家具放置事件
        private void LogFurniturePlacement(string furnitureName, Vector3 position)
        {
            Debug.Log($"家具放置记录 - 名称: {furnitureName}, 位置: {position}, 时间: {Time.time}");
            
            // 在实际商业项目中,这里会收集用户行为数据
            // 例如:哪些家具最受欢迎,用户通常放置的位置等
        }
        
        // 记录家具选择事件
        private void LogFurnitureSelection(string furnitureName)
        {
            Debug.Log($"家具选择记录 - 名称: {furnitureName}, 时间: {Time.time}");
        }
        
        // 记录家具旋转事件
        private void LogFurnitureRotation(float angle)
        {
            Debug.Log($"家具旋转记录 - 角度: {angle}, 时间: {Time.time}");
        }
        
        // 记录家具缩放事件
        private void LogFurnitureScaling(float scaleFactor)
        {
            Debug.Log($"家具缩放记录 - 缩放系数: {scaleFactor}, 时间: {Time.time}");
        }
        
        // 记录家具删除事件
        private void LogFurnitureDeletion(string furnitureName)
        {
            Debug.Log($"家具删除记录 - 名称: {furnitureName}, 时间: {Time.time}");
        }
    }
    
    // 家具控制器(处理单个家具的交互)
    public class FurnitureController : MonoBehaviour
    {
        // 父级布置管理器
        private ARFurniturePlacer furniturePlacer;
        
        // 交互状态
        private bool isSelected = false;
        private bool isDragging = false;
        
        // 拖拽相关变量
        private Vector3 dragOffset;
        private float dragDistance;
        
        // 初始化
        public void Initialize(ARFurniturePlacer placer)
        {
            furniturePlacer = placer;
        }
        
        // 当家具被点击时调用
        private void OnMouseDown()
        {
            if (furniturePlacer == null)
            {
                return;
            }
            
            isSelected = true;
            
            // 计算拖拽偏移
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Plane plane = new Plane(Vector3.up, transform.position);
            
            if (plane.Raycast(ray, out dragDistance))
            {
                Vector3 hitPoint = ray.GetPoint(dragDistance);
                dragOffset = transform.position - hitPoint;
            }
        }
        
        // 当拖拽家具时调用
        private void OnMouseDrag()
        {
            if (!isSelected || furniturePlacer == null)
            {
                return;
            }
            
            isDragging = true;
            
            // 计算新的位置
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Plane plane = new Plane(Vector3.up, transform.position);
            
            if (plane.Raycast(ray, out dragDistance))
            {
                Vector3 hitPoint = ray.GetPoint(dragDistance);
                transform.position = hitPoint + dragOffset;
            }
        }
        
        // 当释放家具时调用
        private void OnMouseUp()
        {
            if (isDragging)
            {
                // 拖拽结束,记录最终位置
                Debug.Log($"家具拖拽完成,最终位置: {transform.position}");
            }
            
            isSelected = false;
            isDragging = false;
        }
    }
}

2.5 数学优化:提升AR性能的商业价值

在商业AR应用中,性能优化直接关系到用户体验和商业成功。数学优化技术可以显著提升AR应用的运行效率,特别是在移动设备上。

空间分区技术如四叉树(2D)和八叉树(3D)可以加速射线检测和碰撞检测。在大型AR零售应用中,当场景中有数百个虚拟商品时,使用空间分区可以将检测复杂度从O(n)降低到O(log n)。

层次包围盒(BVH) 是另一种优化技术,通过为复杂模型创建简化的包围体积,减少精确碰撞检测的计算量。在AR家具应用中,一个复杂的沙发模型可能有上万个三角形,但使用包围盒后,初步的相交检测只需要计算一个简单的长方体。

using UnityEngine;
using System.Collections.Generic;

namespace ARCommercialDemo.MathOptimization
{
    // 空间分区管理器(简化版四叉树实现)
    public class SpatialPartitionManager : MonoBehaviour
    {
        // 分区节点类
        private class QuadTreeNode
        {
            public Bounds bounds;
            public List<GameObject> objects;
            public QuadTreeNode[] children;
            public int level;
            public int maxLevel;
            public int maxObjects;
            
            public QuadTreeNode(Bounds nodeBounds, int nodeLevel, int maxDepth, int maxObjectsPerNode)
            {
                bounds = nodeBounds;
                level = nodeLevel;
                maxLevel = maxDepth;
                maxObjects = maxObjectsPerNode;
                objects = new List<GameObject>();
            }
            
            // 分割节点
            public void Split()
            {
                if (children != null || level >= maxLevel)
                {
                    return;
                }
                
                children = new QuadTreeNode[4];
                
                Vector3 childSize = bounds.size / 2f;
                Vector3 parentCenter = bounds.center;
                
                // 创建四个子节点
                for (int i = 0; i < 4; i++)
                {
                    Vector3 childCenter = parentCenter;
                    
                    switch (i)
                    {
                        case 0: // 左下
                            childCenter.x -= childSize.x / 2f;
                            childCenter.z -= childSize.z / 2f;
                            break;
                        case 1: // 右下
                            childCenter.x += childSize.x / 2f;
                            childCenter.z -= childSize.z / 2f;
                            break;
                        case 2: // 左上
                            childCenter.x -= childSize.x / 2f;
                            childCenter.z += childSize.z / 2f;
                            break;
                        case 3: // 右上
                            childCenter.x += childSize.x / 2f;
                            childCenter.z += childSize.z / 2f;
                            break;
                    }
                    
                    Bounds childBounds = new Bounds(childCenter, childSize);
                    children[i] = new QuadTreeNode(childBounds, level + 1, maxLevel, maxObjects);
                }
            }
            
            // 插入对象
            public bool Insert(GameObject obj)
            {
                // 检查对象是否在节点范围内
                Renderer objRenderer = obj.GetComponent<Renderer>();
                if (objRenderer == null || !bounds.Intersects(objRenderer.bounds))
                {
                    return false;
                }
                
                // 如果还有空间且未分割,直接添加到当前节点
                if (objects.Count < maxObjects && children == null)
                {
                    objects.Add(obj);
                    return true;
                }
                
                // 如果达到容量限制但未分割,先分割
                if (children == null)
                {
                    Split();
                    
                    // 将现有对象重新分配到子节点
                    foreach (GameObject existingObj in objects)
                    {
                        Redistribute(existingObj);
                    }
                    objects.Clear();
                }
                
                // 尝试将对象插入子节点
                for (int i = 0; i < 4; i++)
                {
                    if (children[i].Insert(obj))
                    {
                        return true;
                    }
                }
                
                // 如果对象跨越多个子节点,留在父节点
                objects.Add(obj);
                return true;
            }
            
            // 重新分配对象到子节点
            private void Redistribute(GameObject obj)
            {
                Renderer objRenderer = obj.GetComponent<Renderer>();
                if (objRenderer == null)
                {
                    return;
                }
                
                bool inserted = false;
                
                for (int i = 0; i < 4; i++)
                {
                    if (children[i].bounds.Intersects(objRenderer.bounds))
                    {
                        if (children[i].Insert(obj))
                        {
                            inserted = true;
                        }
                    }
                }
                
                // 如果对象未插入任何子节点,留在当前节点
                if (!inserted)
                {
                    objects.Add(obj);
                }
            }
            
            // 查询范围内的对象
            public void QueryRange(Bounds range, List<GameObject> result)
            {
                // 如果查询范围与节点不相交,直接返回
                if (!bounds.Intersects(range))
                {
                    return;
                }
                
                // 添加当前节点的对象
                foreach (GameObject obj in objects)
                {
                    Renderer objRenderer = obj.GetComponent<Renderer>();
                    if (objRenderer != null && range.Intersects(objRenderer.bounds))
                    {
                        result.Add(obj);
                    }
                }
                
                // 查询子节点
                if (children != null)
                {
                    for (int i = 0; i < 4; i++)
                    {
                        children[i].QueryRange(range, result);
                    }
                }
            }
        }
        
        // 根节点
        private QuadTreeNode rootNode;
        
        // 分区参数
        public Bounds worldBounds = new Bounds(Vector3.zero, new Vector3(100f, 0f, 100f));
        public int maxDepth = 5;
        public int maxObjectsPerNode = 8;
        
        // 所有管理的对象
        private Dictionary<GameObject, bool> managedObjects = new Dictionary<GameObject, bool>();
        
        // 初始化
        private void Start()
        {
            InitializeQuadtree();
            Debug.Log("空间分区管理器已初始化");
        }
        
        // 初始化四叉树
        private void InitializeQuadtree()
        {
            rootNode = new QuadTreeNode(worldBounds, 0, maxDepth, maxObjectsPerNode);
        }
        
        // 注册对象到空间分区系统
        public void RegisterObject(GameObject obj)
        {
            if (obj == null || managedObjects.ContainsKey(obj))
            {
                return;
            }
            
            // 确保对象有Renderer组件
            if (obj.GetComponent<Renderer>() == null)
            {
                Debug.LogWarning($"对象 {obj.name} 没有Renderer组件,无法注册到空间分区");
                return;
            }
            
            if (rootNode.Insert(obj))
            {
                managedObjects[obj] = true;
                Debug.Log($"对象 {obj.name} 已注册到空间分区系统");
            }
        }
        
        // 从空间分区系统移除对象
        public void UnregisterObject(GameObject obj)
        {
            // 注意:简化实现,实际需要从树中递归移除
            managedObjects.Remove(obj);
        }
        
        // 查询范围内的所有对象
        public List<GameObject> QueryObjectsInRange(Bounds range)
        {
            List<GameObject> result = new List<GameObject>();
            
            if (rootNode != null)
            {
                rootNode.QueryRange(range, result);
            }
            
            return result;
        }
        
        // 执行优化的射线检测
        public GameObject RaycastOptimized(Ray ray, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity)
        {
            hitInfo = new RaycastHit();
            
            // 首先使用空间分区缩小检测范围
            // 创建沿射线的边界框用于查询
            Vector3 rayEnd = ray.origin + ray.direction * maxDistance;
            Bounds rayBounds = new Bounds((ray.origin + rayEnd) / 2f, Vector3.zero);
            rayBounds.Encapsulate(ray.origin);
            rayBounds.Encapsulate(rayEnd);
            
            // 扩展边界框以包含可能的相交对象
            rayBounds.Expand(5f);
            
            // 查询可能相交的对象
            List<GameObject> potentialHits = QueryObjectsInRange(rayBounds);
            
            // 执行精确的射线检测
            GameObject closestHit = null;
            float closestDistance = Mathf.Infinity;
            
            foreach (GameObject obj in potentialHits)
            {
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, maxDistance) && hit.collider.gameObject == obj)
                {
                    if (hit.distance < closestDistance)
                    {
                        closestDistance = hit.distance;
                        closestHit = obj;
                        hitInfo = hit;
                    }
                }
            }
            
            return closestHit;
        }
        
        // 性能分析:比较优化前后的检测效率
        public void PerformanceAnalysis(int testIterations = 1000)
        {
            if (managedObjects.Count == 0)
            {
                Debug.Log("没有已注册的对象,无法进行性能分析");
                return;
            }
            
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            
            // 测试传统射线检测
            stopwatch.Start();
            for (int i = 0; i < testIterations; i++)
            {
                Ray randomRay = GenerateRandomRay();
                TraditionalRaycast(randomRay, 100f);
            }
            stopwatch.Stop();
            long traditionalTime = stopwatch.ElapsedMilliseconds;
            
            // 测试优化后的射线检测
            stopwatch.Restart();
            for (int i = 0; i < testIterations; i++)
            {
                Ray randomRay = GenerateRandomRay();
                RaycastHit hitInfo;
                RaycastOptimized(randomRay, out hitInfo, 100f);
            }
            stopwatch.Stop();
            long optimizedTime = stopwatch.ElapsedMilliseconds;
            
            // 输出分析结果
            Debug.Log($"性能分析结果({testIterations}次迭代):");
            Debug.Log($"传统射线检测耗时:{traditionalTime} ms");
            Debug.Log($"优化射线检测耗时:{optimizedTime} ms");
            Debug.Log($"性能提升:{((traditionalTime - optimizedTime) / (float)traditionalTime * 100f):F1}%");
        }
        
        // 生成随机射线(用于测试)
        private Ray GenerateRandomRay()
        {
            Vector3 randomOrigin = new Vector3(
                Random.Range(-worldBounds.extents.x, worldBounds.extents.x),
                10f,
                Random.Range(-worldBounds.extents.z, worldBounds.extents.z)
            );
            
            Vector3 randomDirection = new Vector3(
                Random.Range(-1f, 1f),
                -1f,
                Random.Range(-1f, 1f)
            ).normalized;
            
            return new Ray(randomOrigin, randomDirection);
        }
        
        // 传统射线检测(用于对比)
        private GameObject TraditionalRaycast(Ray ray, float maxDistance)
        {
            RaycastHit[] allHits = Physics.RaycastAll(ray, maxDistance);
            
            GameObject closestHit = null;
            float closestDistance = Mathf.Infinity;
            
            foreach (RaycastHit hit in allHits)
            {
                if (hit.distance < closestDistance && managedObjects.ContainsKey(hit.collider.gameObject))
                {
                    closestDistance = hit.distance;
                    closestHit = hit.collider.gameObject;
                }
            }
            
            return closestHit;
        }
        
        // 可视化调试:绘制四叉树边界
        private void OnDrawGizmosSelected()
        {
            if (rootNode == null)
            {
                return;
            }
            
            DrawNodeGizmos(rootNode);
        }
        
        // 递归绘制节点边界
        private void DrawNodeGizmos(QuadTreeNode node)
        {
            if (node == null)
            {
                return;
            }
            
            // 设置颜色基于节点深度
            float depthRatio = node.level / (float)maxDepth;
            Gizmos.color = new Color(1f - depthRatio, depthRatio, 0f, 0.3f);
            
            // 绘制节点边界
            Gizmos.DrawWireCube(node.bounds.center, node.bounds.size);
            
            // 绘制节点中的对象数量
            #if UNITY_EDITOR
            UnityEditor.Handles.Label(node.bounds.center, $"Objects: {node.objects.Count}");
            #endif
            
            // 递归绘制子节点
            if (node.children != null)
            {
                foreach (QuadTreeNode child in node.children)
                {
                    if (child != null)
                    {
                        DrawNodeGizmos(child);
                    }
                }
            }
        }
    }
    
    // 层次包围盒管理器
    public class BVHManager : MonoBehaviour
    {
        // 包围盒节点类
        private class BVHNode
        {
            public Bounds bounds;
            public GameObject gameObject;
            public BVHNode leftChild;
            public BVHNode rightChild;
            public bool isLeaf;
            
            public BVHNode(GameObject obj)
            {
                gameObject = obj;
                bounds = CalculateObjectBounds(obj);
                isLeaf = true;
            }
            
            public BVHNode(BVHNode left, BVHNode right)
            {
                leftChild = left;
                rightChild = right;
                bounds = Encapsulate(left.bounds, right.bounds);
                isLeaf = false;
            }
            
            // 计算对象的包围盒
            private Bounds CalculateObjectBounds(GameObject obj)
            {
                Renderer renderer = obj.GetComponent<Renderer>();
                if (renderer != null)
                {
                    return renderer.bounds;
                }
                
                // 如果没有Renderer,使用所有子物体的包围盒
                Renderer[] childRenderers = obj.GetComponentsInChildren<Renderer>();
                if (childRenderers.Length > 0)
                {
                    Bounds combinedBounds = childRenderers[0].bounds;
                    for (int i = 1; i < childRenderers.Length; i++)
                    {
                        combinedBounds.Encapsulate(childRenderers[i].bounds);
                    }
                    return combinedBounds;
                }
                
                // 默认包围盒
                return new Bounds(obj.transform.position, Vector3.one);
            }
            
            // 合并两个包围盒
            private Bounds Encapsulate(Bounds a, Bounds b)
            {
                Bounds result = a;
                result.Encapsulate(b);
                return result;
            }
        }
        
        // BVH根节点
        private BVHNode rootNode;
        
        // 所有叶子节点
        private List<BVHNode> leafNodes = new List<BVHNode>();
        
        // 构建BVH
        public void BuildBVH(List<GameObject> objects)
        {
            if (objects == null || objects.Count == 0)
            {
                return;
            }
            
            // 创建叶子节点
            leafNodes.Clear();
            foreach (GameObject obj in objects)
            {
                leafNodes.Add(new BVHNode(obj));
            }
            
            // 递归构建BVH
            rootNode = BuildBVHRecursive(leafNodes);
            
            Debug.Log($"BVH构建完成,共{objects.Count}个对象");
        }
        
        // 递归构建BVH
        private BVHNode BuildBVHRecursive(List<BVHNode> nodes)
        {
            if (nodes.Count == 1)
            {
                return nodes[0];
            }
            
            // 找到最佳分割轴
            int splitAxis = FindBestSplitAxis(nodes);
            
            // 按分割轴排序
            nodes.Sort((a, b) => a.bounds.center[splitAxis].CompareTo(b.bounds.center[splitAxis]));
            
            // 分割节点
            int midIndex = nodes.Count / 2;
            List<BVHNode> leftNodes = nodes.GetRange(0, midIndex);
            List<BVHNode> rightNodes = nodes.GetRange(midIndex, nodes.Count - midIndex);
            
            // 递归构建左右子树
            BVHNode leftChild = BuildBVHRecursive(leftNodes);
            BVHNode rightChild = BuildBVHRecursive(rightNodes);
            
            return new BVHNode(leftChild, rightChild);
        }
        
        // 找到最佳分割轴
        private int FindBestSplitAxis(List<BVHNode> nodes)
        {
            if (nodes.Count <= 1)
            {
                return 0;
            }
            
            // 计算所有节点的总包围盒
            Bounds totalBounds = nodes[0].bounds;
            for (int i = 1; i < nodes.Count; i++)
            {
                totalBounds.Encapsulate(nodes[i].bounds);
            }
            
            // 选择尺寸最大的轴
            Vector3 size = totalBounds.size;
            if (size.x >= size.y && size.x >= size.z)
            {
                return 0; // X轴
            }
            else if (size.y >= size.x && size.y >= size.z)
            {
                return 1; // Y轴
            }
            else
            {
                return 2; // Z轴
            }
        }
        
        // BVH射线检测
        public bool BVHRaycast(Ray ray, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity)
        {
            hitInfo = new RaycastHit();
            return BVHRaycastRecursive(rootNode, ray, ref hitInfo, maxDistance);
        }
        
        // 递归BVH射线检测
        private bool BVHRaycastRecursive(BVHNode node, Ray ray, ref RaycastHit hitInfo, float maxDistance)
        {
            if (node == null || !node.bounds.IntersectRay(ray))
            {
                return false;
            }
            
            if (node.isLeaf)
            {
                // 叶子节点:执行精确检测
                return Physics.Raycast(ray, out hitInfo, maxDistance) && 
                       hitInfo.collider.gameObject == node.gameObject;
            }
            else
            {
                // 内部节点:先检测左子树,再检测右子树
                RaycastHit leftHit, rightHit;
                bool leftResult = BVHRaycastRecursive(node.leftChild, ray, ref leftHit, maxDistance);
                bool rightResult = BVHRaycastRecursive(node.rightChild, ray, ref rightHit, maxDistance);
                
                if (leftResult && rightResult)
                {
                    // 两个子树都命中,选择更近的
                    if (leftHit.distance < rightHit.distance)
                    {
                        hitInfo = leftHit;
                        return true;
                    }
                    else
                    {
                        hitInfo = rightHit;
                        return true;
                    }
                }
                else if (leftResult)
                {
                    hitInfo = leftHit;
                    return true;
                }
                else if (rightResult)
                {
                    hitInfo = rightHit;
                    return true;
                }
                
                return false;
            }
        }
        
        // 可视化调试:绘制BVH
        private void OnDrawGizmosSelected()
        {
            if (rootNode == null)
            {
                return;
            }
            
            DrawBVHGizmos(rootNode, 0);
        }
        
        // 递归绘制BVH
        private void DrawBVHGizmos(BVHNode node, int depth)
        {
            if (node == null)
            {
                return;
            }
            
            // 设置颜色基于深度
            float depthRatio = depth / 10f;
            Gizmos.color = new Color(depthRatio, 1f - depthRatio, 0f, 0.2f);
            
            // 绘制包围盒
            Gizmos.DrawWireCube(node.bounds.center, node.bounds.size);
            
            // 递归绘制子节点
            if (!node.isLeaf)
            {
                DrawBVHGizmos(node.leftChild, depth + 1);
                DrawBVHGizmos(node.rightChild, depth + 1);
            }
        }
    }
}

2.6 商业项目实战:AR室内设计系统

基于前面讨论的数学原理和优化技术,我们现在可以构建一个完整的商业AR室内设计系统。这个系统将整合空间计算、向量数学、四元数旋转、射线检测和性能优化技术。

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

namespace ARCommercialDemo.FullProject
{
    // AR室内设计系统主控制器
    public class ARInteriorDesignSystem : MonoBehaviour
    {
        [Header("系统组件")]
        public ARFurniturePlacer furniturePlacer;
        public SpatialPartitionManager spatialManager;
        public BVHManager bvhManager;
        
        [Header("UI组件")]
        public GameObject furnitureSelectionPanel;
        public Button[] furnitureCategoryButtons;
        public Button saveDesignButton;
        public Button loadDesignButton;
        public Button clearAllButton;
        
        [Header("设计管理")]
        public Text designInfoText;
        public Slider budgetSlider;
        public Text budgetText;
        
        // 当前设计状态
        private List<GameObject> placedFurniture = new List<GameObject>();
        private float currentBudget = 10000f;
        private float totalCost = 0f;
        
        // 家具价格表(商业数据)
        private Dictionary<string, float> furniturePrices = new Dictionary<string, float>()
        {
            {"Sofa", 1500f},
            {"Chair", 300f},
            {"Table", 800f},
            {"Bed", 2000f},
            {"Cabinet", 1200f},
            {"Lamp", 150f},
            {"Bookshelf", 600f}
        };
        
        // 初始化
        private void Start()
        {
            InitializeSystem();
            SetupUI();
            LoadInitialData();
        }
        
        // 初始化系统
        private void InitializeSystem()
        {
            if (furniturePlacer == null)
            {
                furniturePlacer = FindObjectOfType<ARFurniturePlacer>();
            }
            
            if (spatialManager == null)
            {
                spatialManager = FindObjectOfType<SpatialPartitionManager>();
            }
            
            if (bvhManager == null)
            {
                bvhManager = FindObjectOfType<BVHManager>();
            }
            
            Debug.Log("AR室内设计系统初始化完成");
        }
        
        // 设置UI
        private void SetupUI()
        {
            // 设置家具类别按钮
            for (int i = 0; i < furnitureCategoryButtons.Length; i++)
            {
                int categoryIndex = i;
                furnitureCategoryButtons[i].onClick.AddListener(() => SelectFurnitureCategory(categoryIndex));
            }
            
            // 设置功能按钮
            if (saveDesignButton != null)
            {
                saveDesignButton.onClick.AddListener(SaveCurrentDesign);
            }
            
            if (loadDesignButton != null)
            {
                loadDesignButton.onClick.AddListener(LoadDesign);
            }
            
            if (clearAllButton != null)
            {
                clearAllButton.onClick.AddListener(ClearAllFurniture);
            }
            
            // 设置预算滑块
            if (budgetSlider != null)
            {
                budgetSlider.onValueChanged.AddListener(UpdateBudget);
                budgetSlider.value = currentBudget / 20000f; // 假设最大预算20000
            }
            
            UpdateDesignInfo();
        }
        
        // 加载初始数据
        private void LoadInitialData()
        {
            // 在实际商业项目中,这里会从服务器加载家具数据、用户偏好等
            Debug.Log("加载初始数据...");
            
            // 模拟数据加载
            Invoke("SimulateDataLoading", 1.0f);
        }
        
        // 模拟数据加载
        private void SimulateDataLoading()
        {
            Debug.Log("数据加载完成");
            
            // 初始化空间分区系统
            if (spatialManager != null)
            {
                // 注册已存在的家具
                GameObject[] existingFurniture = GameObject.FindGameObjectsWithTag("Furniture");
                foreach (GameObject furniture in existingFurniture)
                {
                    spatialManager.RegisterObject(furniture);
                    placedFurniture.Add(furniture);
                }
                
                // 构建BVH
                if (bvhManager != null && placedFurniture.Count > 0)
                {
                    bvhManager.BuildBVH(placedFurniture);
                }
            }
        }
        
        // 选择家具类别
        private void SelectFurnitureCategory(int categoryIndex)
        {
            if (furniturePlacer == null)
            {
                Debug.LogError("家具放置器未初始化");
                return;
            }
            
            furniturePlacer.SelectFurnitureType(categoryIndex);
            
            // 显示类别名称
            string[] categoryNames = {"沙发", "椅子", "桌子", "床", "柜子", "台灯", "书架"};
            if (categoryIndex < categoryNames.Length)
            {
                Debug.Log($"已选择家具类别: {categoryNames[categoryIndex]}");
                ShowMessage($"已选择: {categoryNames[categoryIndex]}");
            }
        }
        
        // 放置家具(由ARFurniturePlacer调用)
        public void OnFurniturePlaced(GameObject furniture)
        {
            if (furniture == null)
            {
                return;
            }
            
            // 添加到已放置列表
            placedFurniture.Add(furniture);
            
            // 注册到空间分区系统
            if (spatialManager != null)
            {
                spatialManager.RegisterObject(furniture);
            }
            
            // 更新BVH
            UpdateBVH();
            
            // 计算成本
            CalculateFurnitureCost(furniture);
            
            // 更新设计信息
            UpdateDesignInfo();
            
            // 检查预算
            CheckBudget();
            
            Debug.Log($"家具已添加: {furniture.name}, 总计 {placedFurniture.Count} 件家具");
        }
        
        // 计算家具成本
        private void CalculateFurnitureCost(GameObject furniture)
        {
            // 从家具名称提取类型
            string furnitureName = furniture.name.ToLower();
            float cost = 0f;
            
            // 简单匹配逻辑(实际项目会使用更精确的匹配)
            if (furnitureName.Contains("sofa"))
            {
                cost = furniturePrices["Sofa"];
            }
            else if (furnitureName.Contains("chair"))
            {
                cost = furniturePrices["Chair"];
            }
            else if (furnitureName.Contains("table"))
            {
                cost = furniturePrices["Table"];
            }
            else if (furnitureName.Contains("bed"))
            {
                cost = furniturePrices["Bed"];
            }
            else if (furnitureName.Contains("cabinet"))
            {
                cost = furniturePrices["Cabinet"];
            }
            else if (furnitureName.Contains("lamp"))
            {
                cost = furniturePrices["Lamp"];
            }
            else if (furnitureName.Contains("bookshelf"))
            {
                cost = furniturePrices["Bookshelf"];
            }
            
            totalCost += cost;
            
            // 添加成本组件到家具(用于显示)
            FurnitureCost furnitureCost = furniture.AddComponent<FurnitureCost>();
            furnitureCost.SetCost(cost);
            
            Debug.Log($"家具成本: ${cost}, 总成本: ${totalCost}");
        }
        
        // 更新BVH
        private void UpdateBVH()
        {
            if (bvhManager != null && placedFurniture.Count > 0)
            {
                bvhManager.BuildBVH(placedFurniture);
            }
        }
        
        // 更新设计信息
        private void UpdateDesignInfo()
        {
            if (designInfoText != null)
            {
                string info = $"设计信息:\n";
                info += $"家具数量: {placedFurniture.Count}\n";
                info += $"总成本: ${totalCost:F2}\n";
                info += $"预算: ${currentBudget:F2}\n";
                info += $"剩余: ${(currentBudget - totalCost):F2}\n";
                
                designInfoText.text = info;
            }
            
            if (budgetText != null)
            {
                budgetText.text = $"预算: ${currentBudget:F2}";
            }
        }
        
        // 更新预算
        private void UpdateBudget(float sliderValue)
        {
            currentBudget = sliderValue * 20000f; // 映射到0-20000范围
            UpdateDesignInfo();
            CheckBudget();
        }
        
        // 检查预算
        private void CheckBudget()
        {
            if (totalCost > currentBudget)
            {
                ShowMessage($"警告: 超出预算 ${(totalCost - currentBudget):F2}!");
                Debug.LogWarning($"预算超支: 当前成本 ${totalCost}, 预算 ${currentBudget}");
                
                // 在实际商业项目中,这里可能会触发额外的逻辑
                // 如:阻止继续添加家具、显示购买提示等
            }
        }
        
        // 保存当前设计
        private void SaveCurrentDesign()
        {
            if (placedFurniture.Count == 0)
            {
                ShowMessage("没有可保存的设计");
                return;
            }
            
            // 创建设计数据
            DesignData designData = new DesignData();
            designData.designName = $"设计_{System.DateTime.Now:yyyyMMdd_HHmmss}";
            designData.creationDate = System.DateTime.Now;
            designData.totalCost = totalCost;
            designData.budget = currentBudget;
            designData.furnitureCount = placedFurniture.Count;
            
            // 收集家具数据
            designData.furnitureItems = new List<FurnitureItemData>();
            foreach (GameObject furniture in placedFurniture)
            {
                FurnitureItemData itemData = new FurnitureItemData();
                itemData.name = furniture.name;
                itemData.position = furniture.transform.position;
                itemData.rotation = furniture.transform.rotation;
                itemData.scale = furniture.transform.localScale;
                
                FurnitureCost costComponent = furniture.GetComponent<FurnitureCost>();
                if (costComponent != null)
                {
                    itemData.cost = costComponent.GetCost();
                }
                
                designData.furnitureItems.Add(itemData);
            }
            
            // 在实际商业项目中,这里会将designData保存到文件或上传到服务器
            string jsonData = JsonUtility.ToJson(designData, true);
            Debug.Log($"设计已保存:\n{jsonData}");
            
            ShowMessage($"设计 '{designData.designName}' 已保存");
            
            // 商业逻辑:记录设计保存事件
            LogDesignEvent("design_saved", designData);
        }
        
        // 加载设计
        private void LoadDesign()
        {
            // 在实际商业项目中,这里会从文件或服务器加载设计数据
            Debug.Log("加载设计...");
            ShowMessage("加载设计功能开发中...");
            
            // 示例:加载示例设计
            LoadExampleDesign();
        }
        
        // 加载示例设计(演示用)
        private void LoadExampleDesign()
        {
            ClearAllFurniture();
            
            // 创建示例家具布局
            Vector3 roomCenter = Vector3.zero;
            
            // 放置一个沙发
            if (furniturePlacer != null && furniturePlacer.furniturePrefabs.Length > 0)
            {
                GameObject sofaPrefab = furniturePlacer.furniturePrefabs[0];
                GameObject sofa = Instantiate(sofaPrefab, roomCenter + new Vector3(0, 0, -1), Quaternion.identity);
                OnFurniturePlaced(sofa);
            }
            
            // 放置一个桌子
            if (furniturePlacer != null && furniturePlacer.furniturePrefabs.Length > 2)
            {
                GameObject tablePrefab = furniturePlacer.furniturePrefabs[2];
                GameObject table = Instantiate(tablePrefab, roomCenter + new Vector3(0, 0, 1), Quaternion.identity);
                OnFurniturePlaced(table);
            }
            
            // 放置两把椅子
            if (furniturePlacer != null && furniturePlacer.furniturePrefabs.Length > 1)
            {
                GameObject chairPrefab = furniturePlacer.furniturePrefabs[1];
                
                GameObject chair1 = Instantiate(chairPrefab, roomCenter + new Vector3(-1, 0, 1), 
                    Quaternion.Euler(0, -90, 0));
                OnFurniturePlaced(chair1);
                
                GameObject chair2 = Instantiate(chairPrefab, roomCenter + new Vector3(1, 0, 1), 
                    Quaternion.Euler(0, 90, 0));
                OnFurniturePlaced(chair2);
            }
            
            ShowMessage("示例设计已加载");
        }
        
        // 清除所有家具
        private void ClearAllFurniture()
        {
            foreach (GameObject furniture in placedFurniture)
            {
                if (furniture != null)
                {
                    Destroy(furniture);
                }
            }
            
            placedFurniture.Clear();
            totalCost = 0f;
            
            // 清空空间分区
            if (spatialManager != null)
            {
                // 注意:简化实现,实际需要更完整的清理
                spatialManager = FindObjectOfType<SpatialPartitionManager>();
            }
            
            UpdateDesignInfo();
            ShowMessage("所有家具已清除");
            
            Debug.Log("所有家具已清除");
        }
        
        // 显示消息
        private void ShowMessage(string message)
        {
            Debug.Log($"系统消息: {message}");
            
            // 在实际商业项目中,这里会显示到UI消息系统
            // 例如:messageText.text = message;
            // 然后使用协程淡出
        }
        
        // 记录设计事件(商业分析)
        private void LogDesignEvent(string eventType, DesignData designData = null)
        {
            Debug.Log($"设计事件: {eventType}, 时间: {System.DateTime.Now}");
            
            // 在实际商业项目中,这里会发送数据到分析服务器
            // 例如:用户保存设计的频率、平均家具数量、常用家具类型等
            
            // SendAnalyticsData("design_event", new {
            //     event_type = eventType,
            //     furniture_count = placedFurniture.Count,
            //     total_cost = totalCost,
            //     timestamp = System.DateTime.Now
            // });
        }
        
        // 性能优化测试
        public void RunPerformanceTest()
        {
            if (spatialManager != null)
            {
                spatialManager.PerformanceAnalysis(1000);
            }
        }
        
        // 导出设计报告(商业功能)
        public void ExportDesignReport()
        {
            if (placedFurniture.Count == 0)
            {
                ShowMessage("没有设计可导出");
                return;
            }
            
            // 生成报告
            string report = "=== AR室内设计报告 ===\n";
            report += $"生成时间: {System.DateTime.Now}\n";
            report += $"家具总数: {placedFurniture.Count}\n";
            report += $"总成本: ${totalCost:F2}\n";
            report += $"预算: ${currentBudget:F2}\n";
            report += $"预算状态: {(totalCost <= currentBudget ? "在预算内" : "超预算")}\n\n";
            
            report += "家具清单:\n";
            report += "----------------------------------------\n";
            
            // 按类型统计
            Dictionary<string, int> typeCount = new Dictionary<string, int>();
            Dictionary<string, float> typeCost = new Dictionary<string, float>();
            
            foreach (GameObject furniture in placedFurniture)
            {
                string type = GetFurnitureType(furniture);
                
                if (!typeCount.ContainsKey(type))
                {
                    typeCount[type] = 0;
                    typeCost[type] = 0f;
                }
                
                typeCount[type]++;
                
                FurnitureCost costComponent = furniture.GetComponent<FurnitureCost>();
                if (costComponent != null)
                {
                    typeCost[type] += costComponent.GetCost();
                }
            }
            
            foreach (var kvp in typeCount)
            {
                string type = kvp.Key;
                int count = kvp.Value;
                float cost = typeCost.ContainsKey(type) ? typeCost[type] : 0f;
                
                report += $"{type}: {count} 件, 总价: ${cost:F2}, 均价: ${(cost/count):F2}\n";
            }
            
            report += "\n=== 报告结束 ===\n";
            
            Debug.Log(report);
            ShowMessage("设计报告已生成(查看控制台)");
            
            // 在实际商业项目中,这里会生成PDF或分享到其他应用
        }
        
        // 获取家具类型
        private string GetFurnitureType(GameObject furniture)
        {
            string name = furniture.name.ToLower();
            
            if (name.Contains("sofa")) return "沙发";
            if (name.Contains("chair")) return "椅子";
            if (name.Contains("table")) return "桌子";
            if (name.Contains("bed")) return "床";
            if (name.Contains("cabinet")) return "柜子";
            if (name.Contains("lamp")) return "台灯";
            if (name.Contains("bookshelf")) return "书架";
            
            return "其他";
        }
        
        // 设计数据类
        [System.Serializable]
        public class DesignData
        {
            public string designName;
            public System.DateTime creationDate;
            public float totalCost;
            public float budget;
            public int furnitureCount;
            public List<FurnitureItemData> furnitureItems;
        }
        
        // 家具数据类
        [System.Serializable]
        public class FurnitureItemData
        {
            public string name;
            public Vector3 position;
            public Quaternion rotation;
            public Vector3 scale;
            public float cost;
        }
    }
    
    // 家具成本组件
    public class FurnitureCost : MonoBehaviour
    {
        private float cost = 0f;
        
        public void SetCost(float furnitureCost)
        {
            cost = furnitureCost;
        }
        
        public float GetCost()
        {
            return cost;
        }
        
        // 显示成本(在Scene视图中)
        private void OnDrawGizmosSelected()
        {
            #if UNITY_EDITOR
            UnityEditor.Handles.Label(
                transform.position + Vector3.up * 0.5f,
                $"${cost:F2}"
            );
            #endif
        }
    }
}

本章通过深入讲解AR开发中的数学基础,结合完整的商业项目实例,展示了如何将数学理论应用于实际的AR开发中。从基本的空间坐标变换到高级的性能优化技术,这些数学原理是构建高质量商业AR应用的基石。通过理解和应用这些概念,开发者可以创建出更加精准、高效和用户友好的AR体验,为商业应用带来真正的价值。

更多推荐