345 lines
13 KiB
C#
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;
|
|
}
|
|
|
|
}
|
|
|
|
}
|