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 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 _deferredActions; public simBotella(Simulation simulation, List 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(); // ✅ 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 } /// /// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual /// 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}"); } } /// /// ✅ NUEVO: Crea un motor específico para un transporte o curva /// 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}"); } } /// /// ✅ NUEVO: Actualiza solo la velocidad del motor existente /// 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}"); } } /// /// ✅ NUEVO: Elimina el motor actual /// 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; } } /// /// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión /// public void StopMotor() { UpdateMotorSpeed(CurrentDirection, 0f); } /// /// ✅ NUEVO: Limpia las restricciones asociadas a un elemento específico /// Solo limpia los datos internos de simBotella, no elimina las restricciones de BEPU /// /// Elemento (simTransporte o simCurve) del cual limpiar las restricciones 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; } /// /// 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 /// 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; } /// /// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints /// public new void RemoverBody() { base.RemoverBody(); } } }