CtrEditor/Simulacion/simBarrera.cs

345 lines
13 KiB
C#

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
{
/// <summary>
/// ✅ CORREGIDO: RayHitHandler personalizado para simBarrera
/// Calcula la distancia mínima del rayo al centro de cada botella
/// </summary>
public struct BarreraRayHitHandler : IRayHitHandler
{
private simBarrera _barrera;
private List<simBotella> _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<simBotella>();
_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}");
}
}
/// <summary>
/// ✅ NUEVO: Calcula la distancia mínima de un rayo (línea) a un punto
/// </summary>
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<simBotella> botellas)
{
luzCortada = _fullDetected;
luzCortadaNeck = _neckDetected;
distancia = _minDistance == float.MaxValue ? 0f : _minDistance;
botellas = new List<simBotella>(_detectedBottles);
}
}
/// <summary>
/// Estructura para datos de barrera que se exponen a WPF
/// </summary>
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<simBotella> 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<simBotella>();
// ✅ 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<simBotella>();
// ✅ 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);
}
/// <summary>
/// ✅ INTERNO: Actualiza posición y rotación desde parámetros BEPU internos
/// </summary>
internal void Update(Vector3 bepuPosition, float bepuRadians)
{
Position = bepuPosition;
AngleRadians = bepuRadians;
}
public void SetDimensions(float width, float height)
{
Width = width;
Height = height;
}
/// <summary>
/// ✅ INTERNO: Crea la posición usando parámetros BEPU internos
/// </summary>
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;
}
/// <summary>
/// ✅ CORREGIDO: Realiza raycast para detectar botellas usando coordenadas BEPU internas
/// </summary>
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<simBotella> 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}");
}
}
/// <summary>
/// ✅ INTERNO: Métodos Create que trabajan solo con parámetros BEPU internos
/// </summary>
internal void Create(float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false)
{
CreatePosition(width, height, bepuPosition, bepuRadians);
}
/// <summary>
/// ✅ OVERRIDE: RemoverBody no hace nada porque no hay body físico
/// </summary>
public new void RemoverBody()
{
// ✅ NO HAY BODY FÍSICO QUE REMOVER
_bodyCreated = false;
_poseInitialized = false;
}
}
}