using BepuPhysics; using BepuPhysics.Collidables; using BepuPhysics.Trees; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; namespace CtrEditor.Simulacion { /// /// ✅ CORREGIDO: RayHitHandler personalizado para simBarrera /// Calcula la distancia mínima del rayo al centro de cada botella /// public struct BarreraRayHitHandler : IRayHitHandler { private simBarrera _barrera; private List _detectedBottles; private SimulationManagerBEPU _simulationManager; private float _minDistance; private bool _neckDetected; private bool _fullDetected; private Vector3 _rayOrigin; private Vector3 _rayDirection; internal BarreraRayHitHandler(simBarrera barrera, SimulationManagerBEPU simulationManager, Vector3 rayOrigin, Vector3 rayDirection) { _barrera = barrera; _simulationManager = simulationManager; _detectedBottles = new List(); _minDistance = float.MaxValue; _neckDetected = false; _fullDetected = false; _rayOrigin = rayOrigin; _rayDirection = rayDirection; } public bool AllowTest(CollidableReference collidable) { // Solo testear botellas dinámicas if (collidable.Mobility == CollidableMobility.Dynamic) { var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella; return bottle != null; } return false; } public bool AllowTest(CollidableReference collidable, int childIndex) { // Para botellas (esferas), childIndex siempre es 0 return AllowTest(collidable); } public void OnRayHit(in RayData ray, ref float maximumT, float t, Vector3 normal, CollidableReference collidable, int childIndex) { try { // Obtener la botella que fue hit var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella; if (bottle == null) return; // Obtener posición del centro de la botella var bottleCenter = bottle.GetPosition(); // ✅ CORREGIDO: Calcular la distancia mínima del rayo (línea) al centro de la botella var minDistanceToCenter = CalculateMinimumDistanceFromRayToPoint(_rayOrigin, _rayDirection, bottleCenter); // Actualizar distancia mínima global if (minDistanceToCenter < _minDistance) { _minDistance = minDistanceToCenter; } // ✅ CORREGIDO: Verificar detección completa usando distancia mínima del rayo al centro if (minDistanceToCenter <= bottle.Radius) { _fullDetected = true; if (!_detectedBottles.Contains(bottle)) { _detectedBottles.Add(bottle); } } // ✅ CORREGIDO: Verificar detección de cuello usando circunferencia imaginaria con radio/2 if (_barrera.DetectNeck && minDistanceToCenter <= (bottle.Radius / 2f)) { _neckDetected = true; } // System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Hit: Botella={bottle.BodyHandle}, distanciaMinima={minDistanceToCenter:F3}, radio={bottle.Radius:F3}"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Error en OnRayHit: {ex.Message}"); } } /// /// ✅ NUEVO: Calcula la distancia mínima de un rayo (línea) a un punto /// private float CalculateMinimumDistanceFromRayToPoint(Vector3 rayOrigin, Vector3 rayDirection, Vector3 point) { // Vector desde el origen del rayo al punto var rayToPoint = point - rayOrigin; // Proyección del vector rayToPoint sobre la dirección del rayo var projection = Vector3.Dot(rayToPoint, rayDirection); // Punto más cercano en el rayo al punto dado var closestPointOnRay = rayOrigin + rayDirection * projection; // Distancia mínima del rayo al punto return Vector3.Distance(closestPointOnRay, point); } // ✅ NUEVO: Método para obtener los resultados del raycast public void GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List botellas) { luzCortada = _fullDetected; luzCortadaNeck = _neckDetected; distancia = _minDistance == float.MaxValue ? 0f : _minDistance; botellas = new List(_detectedBottles); } } /// /// Estructura para datos de barrera que se exponen a WPF /// public struct BarreraData { public bool LuzCortada; public bool LuzCortadaNeck; public BarreraData(bool luzCortada, bool luzCortadaNeck) { LuzCortada = luzCortada; LuzCortadaNeck = luzCortadaNeck; } } public class simBarrera : simBase { internal bool LuzCortada; internal bool LuzCortadaNeck; public List ListSimBotellaContact; public float Width { get; set; } public float Height { get; set; } public bool DetectNeck { get; set; } // ✅ NUEVO: Propiedades para raycast private SimulationManagerBEPU _simulationManager; // ✅ CORREGIDO: Almacenar ángulo internamente como radianes BEPU (como simCurve) private float _angleRadians; // ✅ SIEMPRE en radianes BEPU (ya convertido desde WPF) internal simBarrera(Simulation simulation, float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false, SimulationManagerBEPU simulationManager = null) { _simulation = simulation; _simulationManager = simulationManager; Width = width; Height = height; DetectNeck = detectectNeck; ListSimBotellaContact = new List(); // ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos CreatePosition(width, height, bepuPosition, bepuRadians); } internal void Update(float width, float height, Vector3 bepuPosition, float bepuRadians = 0) { Width = width; Height = height; ListSimBotellaContact = new List(); // ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos CreatePosition(width, height, bepuPosition, bepuRadians); } // ✅ NUEVO: Variables para almacenar pose sin crear body físico private Vector3 _position; private Quaternion _orientation; private bool _poseInitialized = false; // ✅ NUEVO: Propiedad pública para acceder a la posición public Vector3 Position { get => _poseInitialized ? _position : Vector3.Zero; set { _position = value; _poseInitialized = true; } } // ✅ NUEVO: Propiedad pública para acceder al ángulo en radianes BEPU public float AngleRadians { get => _poseInitialized ? _angleRadians : 0f; set { _angleRadians = value; UpdateOrientationFromInternalAngle(); _poseInitialized = true; } } // ✅ NUEVO: Método interno para actualizar orientación desde ángulo interno private void UpdateOrientationFromInternalAngle() { // ✅ CREAR QUATERNION DESDE RADIANES BEPU INTERNOS _orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, _angleRadians); } /// /// ✅ INTERNO: Actualiza posición y rotación desde parámetros BEPU internos /// internal void Update(Vector3 bepuPosition, float bepuRadians) { Position = bepuPosition; AngleRadians = bepuRadians; } public void SetDimensions(float width, float height) { Width = width; Height = height; } /// /// ✅ INTERNO: Crea la posición usando parámetros BEPU internos /// internal void CreatePosition(float width, float height, Vector3 bepuPosition, float bepuRadians) { Width = width; Height = height; // ✅ ALMACENAR DIRECTAMENTE parámetros BEPU internos Position = bepuPosition; AngleRadians = bepuRadians; // ✅ NO CREAR BODY FÍSICO - solo almacenar datos _bodyCreated = false; } /// /// ✅ CORREGIDO: Realiza raycast para detectar botellas usando coordenadas BEPU internas /// public void PerformRaycast() { try { // Resetear flags LuzCortada = false; LuzCortadaNeck = false; ListSimBotellaContact?.Clear(); // Validar que tenemos simulación y manager if (_simulation == null || _simulationManager == null || !_poseInitialized) { System.Diagnostics.Debug.WriteLine("[PerformRaycast] Simulación, manager o pose no inicializados"); return; } // ✅ CORREGIDO: Usar coordenadas BEPU internas para cálculos var bepuPosition = Position; var Orientation = _orientation; // ✅ CORREGIDO: Crear puntos del ray a la altura correcta var halfWidth = Width / 2f; var rayStartLocal = new Vector3(-halfWidth, 0, 0); var rayEndLocal = new Vector3(halfWidth, 0, 0); // ✅ CORREGIDO: Ajustar la posición de la barrera para que el ray esté a la altura correcta var barreraPosition = new Vector3(bepuPosition.X, bepuPosition.Y, bepuPosition.Z); // ✅ TRANSFORMAR PUNTOS LOCALES A COORDENADAS MUNDIALES A LA ALTURA CORRECTA var worldStart = barreraPosition + Vector3.Transform(rayStartLocal, Orientation); var worldEnd = barreraPosition + Vector3.Transform(rayEndLocal, Orientation); // ✅ CALCULAR DIRECCIÓN Y DISTANCIA DEL RAY var rayDirection = worldEnd - worldStart; var rayDistance = rayDirection.Length(); if (rayDistance < 0.001f) { System.Diagnostics.Debug.WriteLine("[PerformRaycast] Ray demasiado corto"); return; } rayDirection = Vector3.Normalize(rayDirection); // ✅ CORREGIDO: CREAR HANDLER PARA PROCESAR HITS con parámetros correctos var rayHandler = new BarreraRayHitHandler(this, _simulationManager, worldStart, rayDirection); // ✅ REALIZAR RAYCAST _simulation.RayCast(worldStart, rayDirection, rayDistance, ref rayHandler); // ✅ OBTENER RESULTADOS rayHandler.GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List botellas); // ✅ ASIGNAR RESULTADOS LuzCortada = luzCortada; LuzCortadaNeck = luzCortadaNeck; if (ListSimBotellaContact != null) { ListSimBotellaContact.Clear(); ListSimBotellaContact.AddRange(botellas); } //System.Diagnostics.Debug.WriteLine($"[PerformRaycast] {worldStart} - {worldEnd} - {rayDistance} - {rayDirection} "); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[PerformRaycast] Error: {ex.Message}"); } } /// /// ✅ INTERNO: Métodos Create que trabajan solo con parámetros BEPU internos /// internal void Create(float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false) { CreatePosition(width, height, bepuPosition, bepuRadians); } /// /// ✅ OVERRIDE: RemoverBody no hace nada porque no hay body físico /// public new void RemoverBody() { // ✅ NO HAY BODY FÍSICO QUE REMOVER _bodyCreated = false; _poseInitialized = false; } } }