CtrEditor/Simulacion/simBotella.cs

554 lines
23 KiB
C#

using BepuPhysics.Collidables;
using BepuPhysics.Constraints;
using BepuPhysics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using DocumentFormat.OpenXml.Spreadsheet;
namespace CtrEditor.Simulacion
{
public class simBotella : simBase
{
public float Radius;
public float Height; // Altura para la visualización del cilindro en Helix
private float _mass;
public bool Descartar = false;
public int isOnTransports;
public List<simBase> ListOnTransports;
public bool isRestricted;
public bool isNoMoreRestricted;
public simTransporte ConveyorRestrictedTo;
public float OverlapPercentage;
public float _neckRadius;
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
// ✅ NUEVO SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario
public ConstraintHandle CurrentMotor { get; private set; } = default;
public ConstraintHandle CurrentDistanceLimit { get; private set; } = default; // ✅ NUEVO: Para curvas
public simBase CurrentMotorTarget { get; private set; } = null; // Transporte o curva actual
public bool _hasMotor = false; // ✅ BANDERA PÚBLICA para acceso desde callbacks
public bool HasMotor => _hasMotor;
// ✅ NUEVAS PROPIEDADES para el motor dinámico
public Vector3 CurrentDirection { get; private set; } = Vector3.UnitX; // Dirección por defecto (1,0,0)
public float CurrentSpeed { get; private set; } = 0f; // Velocidad por defecto
public bool IsOnElement { get; private set; } = false; // Si está sobre un elemento activo
private List<Action> _deferredActions;
public simBotella(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
{
_simulation = simulation;
_deferredActions = deferredActions;
Radius = diameter / 2f;
Height = diameter; // Altura igual al diámetro para mantener proporciones similares
_mass = mass;
_neckRadius = neckRadius;
ListOnTransports = new List<simBase>();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
Create(position3D);
// ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario
}
public float CenterX
{
get
{
return GetPosition().X;
}
set
{
var pos = GetPosition();
SetPosition(value, pos.Y, pos.Z);
}
}
public float CenterY
{
get
{
// ✅ USAR COORDINATECONVERTER para conversión centralizada
return CoordinateConverter.BepuYToWpfY(GetPosition().Y);
}
set
{
var pos = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z);
}
}
public Vector2 Center
{
get
{
var pos3D = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
return CoordinateConverter.BepuVector3ToWpfVector2(pos3D);
}
set
{
// Mantener la Z actual, solo cambiar X, Y
var currentPos = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z);
}
}
public float Mass
{
get
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
return 1f / bodyReference.LocalInertia.InverseMass;
}
return _mass;
}
set
{
_mass = value;
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
// Usar esfera simple - sin complejidad de inercia personalizada
var sphere = new Sphere(Radius);
var inertia = sphere.ComputeInertia(value);
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
}
}
}
private void Create(Vector3 position)
{
RemoverBody();
// Usar ESFERA en BEPU para simplicidad matemática y eficiencia
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
// Inercia estándar de esfera - sin complejidad adicional
var inertia = sphere.ComputeInertia(_mass);
// NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode
// Esto es crítico para detección continua de barreras, transportes y descartes
var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir
var bodyDescription = BodyDescription.CreateDynamic(
new RigidPose(position),
new BodyVelocity(),
inertia, // Inercia estándar de esfera
new CollidableDescription(shapeIndex, 0.001f),
activityDescription
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; // Marcar que hemos creado un cuerpo
}
/// <summary>
/// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual
/// </summary>
public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed)
{
try
{
// ✅ VALIDAR DIRECCIÓN
if (direction.Length() < 0.001f)
{
return;
}
// ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN
if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
{
return;
}
// ✅ VERIFICAR SI NECESITAMOS CREAR O ACTUALIZAR EL MOTOR
bool needsNewMotor = false;
if (!HasMotor)
{
// ✅ PRIMERA VEZ: Crear motor nuevo
needsNewMotor = true;
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🆕 Creando motor nuevo para {target?.GetType().Name}");
}
else if (CurrentMotorTarget != target)
{
// ✅ CAMBIO DE OBJETO: Eliminar motor anterior y crear uno nuevo
RemoveCurrentMotor();
needsNewMotor = true;
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🔄 Cambiando motor de {CurrentMotorTarget?.GetType().Name} a {target?.GetType().Name}");
}
else
{
// ✅ MISMO OBJETO: Solo actualizar velocidad
UpdateMotorSpeed(direction, speed / simBase.SpeedConversionFactor);
return;
}
// ✅ CREAR NUEVO MOTOR SI ES NECESARIO
if (needsNewMotor && target != null)
{
CreateMotorForTarget(target, direction, speed / simBase.SpeedConversionFactor);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Crea un motor específico para un transporte o curva
/// </summary>
private void CreateMotorForTarget(simBase target, Vector3 direction, float speed)
{
try
{
if (_simulation == null || _simulation.Solver == null || !_simulation.Bodies.BodyExists(BodyHandle) || target == null)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Simulación, solver, body o target no disponible");
return;
}
// ✅ VERIFICAR QUE EL TARGET TENGA UN BODY VÁLIDO
if (!_simulation.Bodies.BodyExists(target.BodyHandle))
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Target body no existe: {target.BodyHandle}");
return;
}
// ✅ VERIFICAR QUE NO TENGA YA UN MOTOR VÁLIDO
if (HasMotor && CurrentMotor.Value != 0)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ⚠️ Ya existe un motor válido: {CurrentMotor}");
return;
}
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
var tangentDir = Vector3.Normalize(direction);
// ✅ CREAR MOTOR CONECTADO AL TARGET
var motor = new LinearAxisMotor()
{
LocalOffsetA = Vector3.Zero, // Target
LocalOffsetB = Vector3.Zero, // Botella
LocalAxis = tangentDir, // ✅ CORREGIDO: Usar la dirección tangencial calculada
TargetVelocity = speed, // ✅ CORREGIDO: Usar la velocidad directamente
Settings = new MotorSettings(Math.Max(_mass * 20f, 8f), 0f)
};
// ✅ CONECTAR BOTELLA CON EL TARGET (transporte o curva)
CurrentMotor = _simulation.Solver.Add(target.BodyHandle, BodyHandle, motor);
CurrentMotorTarget = target;
_hasMotor = true; // ✅ ESTABLECER BANDERA
//if (target is simCurve curva) {
// // Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
// var curveCenter = curva.CurveCenter;
// var bottlePosition = GetPosition();
// var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
// var radius = radiusVector.Length();
// if (radius > 1e-3f)
// {
// // Calcular offsets locales
// var localOffsetA = curveCenter; // Desde el centro de la curva hasta el punto de anclaje
// var localOffsetB = Vector3.Zero; // ✅ SIMPLIFICADO: Conectar al centro de la botella
// var distanceLimit = new DistanceLimit()
// {
// LocalOffsetA = Vector3.Zero,
// LocalOffsetB = Vector3.Zero,
// MinimumDistance = radius- 4*Radius, // Distancia mínima = radio actual
// MaximumDistance = radius+ 4*Radius, // Distancia máxima = radio actual (mantener distancia fija)
// SpringSettings = new SpringSettings(30f, 0f)
// };
// //CurrentDistanceLimit = _simulation.Solver.Add(target.BodyHandle, BodyHandle, distanceLimit);
// //System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget-Curve] 📏 DistanceLimit creado:");
// //System.Diagnostics.Debug.WriteLine($" Radio actual: {radius:F3}");
// //System.Diagnostics.Debug.WriteLine($" Punto de anclaje: {anchorPoint}");
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetA (curva): {localOffsetA}");
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetB (botella): {localOffsetB} (centro)");
// //System.Diagnostics.Debug.WriteLine($" Distancia objetivo: {radius:F3}");
// //System.Diagnostics.Debug.WriteLine($" DistanceLimit Handle: {CurrentDistanceLimit}");
// }
//}
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
CurrentDirection = direction;
CurrentSpeed = speed;
IsOnElement = Math.Abs(speed) > 0.001f;
//System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ✅ Motor creado:");
//System.Diagnostics.Debug.WriteLine($" Botella: {BodyHandle}");
//System.Diagnostics.Debug.WriteLine($" Target: {target.BodyHandle} ({target.GetType().Name})");
//System.Diagnostics.Debug.WriteLine($" Motor Handle: {CurrentMotor} (Value: {CurrentMotor.Value})");
//System.Diagnostics.Debug.WriteLine($" Dirección: {direction}");
//System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Actualiza solo la velocidad del motor existente
/// </summary>
private void UpdateMotorSpeed(Vector3 direction, float speed)
{
try
{
if (!HasMotor || _simulation == null)
{
return;
}
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
var tangentDir = Vector3.Normalize(direction);
// ✅ OBTENER LA DESCRIPCIÓN ACTUAL DEL MOTOR
_simulation.Solver.GetDescription(CurrentMotor, out LinearAxisMotor motor);
// ✅ ACTUALIZAR DIRECCIÓN Y VELOCIDAD
motor.LocalAxis = tangentDir;
motor.TargetVelocity = speed;
// ✅ ACTUALIZAR EL MOTOR EN EL SOLVER
_simulation.Solver.ApplyDescription(CurrentMotor, motor);
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
CurrentDirection = direction;
CurrentSpeed = speed;
IsOnElement = Math.Abs(speed) > 0.001f;
//System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] 🔄 Velocidad actualizada: {effectiveSpeed:F3}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] ❌ ERROR: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Elimina el motor actual
/// </summary>
public void RemoveCurrentMotor()
{
try
{
if (HasMotor && _simulation != null && _simulation.Solver != null)
{
// ✅ VERIFICAR QUE EL MOTOR EXISTE ANTES DE ELIMINARLO
if (CurrentMotor.Value != 0)
{
try
{
_simulation.Solver.Remove(CurrentMotor);
//System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ Motor eliminado: {CurrentMotor}");
}
catch (Exception removeEx)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando motor {CurrentMotor}: {removeEx.Message}");
// Continuar con la limpieza incluso si falla la eliminación
}
}
else
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Motor ya eliminado o inválido: {CurrentMotor}");
}
}
// ✅ NUEVO: Eliminar DistanceLimit si existe
if (CurrentDistanceLimit.Value != 0)
{
try
{
_simulation.Solver.Remove(CurrentDistanceLimit);
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ DistanceLimit eliminado: {CurrentDistanceLimit}");
}
catch (Exception removeEx)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando DistanceLimit {CurrentDistanceLimit}: {removeEx.Message}");
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ❌ ERROR: {ex.Message}");
}
finally
{
// ✅ LIMPIAR REFERENCIAS SIEMPRE
CurrentMotor = default;
CurrentDistanceLimit = default; // ✅ NUEVO: Limpiar DistanceLimit
CurrentMotorTarget = null;
_hasMotor = false; // ✅ LIMPIAR BANDERA
CurrentDirection = Vector3.UnitX;
CurrentSpeed = 0f;
IsOnElement = false;
}
}
/// <summary>
/// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión
/// </summary>
public void StopMotor()
{
UpdateMotorSpeed(CurrentDirection, 0f);
}
/// <summary>
/// ✅ NUEVO: Limpia las restricciones asociadas a un elemento específico
/// Solo limpia los datos internos de simBotella, no elimina las restricciones de BEPU
/// </summary>
/// <param name="target">Elemento (simTransporte o simCurve) del cual limpiar las restricciones</param>
public void CleanRestrictions(simBase target)
{
try
{
// ✅ VERIFICAR SI EL TARGET COINCIDE CON EL ACTUAL
if (CurrentMotorTarget == target)
{
// ✅ LIMPIAR SOLO LOS DATOS INTERNOS (no eliminar restricciones de BEPU)
CurrentMotorTarget = null;
_hasMotor = false; // ✅ CRÍTICO: Limpiar el flag del motor
CurrentDirection = Vector3.UnitX;
CurrentSpeed = 0f;
IsOnElement = false;
// ✅ NO LIMPIAR CurrentMotor ni CurrentDistanceLimit - BEPU los maneja automáticamente
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ✅ Restricciones limpiadas para {target?.GetType().Name}");
}
else
{
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ⚠️ Target no coincide: actual={CurrentMotorTarget?.GetType().Name}, solicitado={target?.GetType().Name}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ❌ ERROR: {ex.Message}");
}
}
public void SetDiameter(float diameter)
{
Radius = diameter / 2f;
Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
ChangeBodyShape(shapeIndex);
}
}
public void SetHeight(float height)
{
Height = height;
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var sphere = new Sphere(Radius);
var shapeIndex = _simulation.Shapes.Add(sphere);
ChangeBodyShape(shapeIndex);
}
}
public void SetMass(float mass)
{
Mass = mass;
}
/// <summary>
/// Limita la rotación de la botella solo al plano XY (siempre "de pie")
/// Esto simplifica enormemente la simulación y es más realista para botellas
/// </summary>
public void LimitRotationToXYPlane()
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
// Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y
var currentOrientation = bodyReference.Pose.Orientation;
// Convertir a ángulo en Z solamente
var rotationZ = GetRotationZ();
// Crear nueva orientación solo con rotación en Z (botella siempre "de pie")
var correctedOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
// Aplicar la orientación corregida
bodyReference.Pose.Orientation = correctedOrientation;
// También limitar la velocidad angular a solo rotación en Z
var angularVelocity = bodyReference.Velocity.Angular;
bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z);
}
}
public void ApplyLinearVelocity(Vector3 velocity)
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
bodyReference.Velocity.Linear = velocity;
}
}
public Vector3 GetLinearVelocity()
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
return bodyReference.Velocity.Linear;
}
return Vector3.Zero;
}
public void CenterFixtureOnConveyor()
{
// Implementar lógica de centrado si es necesario
}
public bool IsOnAnyTransport()
{
return isOnTransports > 0;
}
/// <summary>
/// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints
/// </summary>
public new void RemoverBody()
{
base.RemoverBody();
}
}
}