using BepuPhysics.Collidables; using BepuPhysics; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; namespace CtrEditor.Simulacion { /// /// Representa una curva o arco en la simulación física. /// ✅ IMPORTANTE: Los ángulos _startAngle y _endAngle se almacenan INTERNAMENTE en radianes BEPU /// (ya convertidos desde grados WPF usando CoordinateConverter.WpfDegreesToBepuRadians) /// Las propiedades públicas StartAngle/EndAngle devuelven grados WPF usando CoordinateConverter.BepuRadiansToWpfDegrees /// ✅ NUEVO: Cada triángulo actúa como un mini-transporte con su propia dirección tangencial fija /// public class simCurve : simBase { private float _innerRadius; private float _outerRadius; private float _startAngle; // ✅ SIEMPRE en radianes BEPU private float _endAngle; // ✅ SIEMPRE en radianes BEPU public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s) private List _deferredActions; // ✅ NUEVO: Almacenar el centro real de la curva private Vector3 _curveCenter; // ✅ SIMPLIFICADO: Propiedades esenciales únicamente public List BottlesOnCurve { get; private set; } = new List(); // ✅ NUEVO: Almacenar triángulos creados para acceso directo private Triangle[] _storedTriangles; public float InnerRadius => _innerRadius; public float OuterRadius => _outerRadius; public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF public float EndAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_endAngle); // Convertir de radianes BEPU internos a grados WPF // ✅ NUEVO: Propiedad para acceder al centro real de la curva public Vector3 CurveCenter => _curveCenter; public simCurve(Simulation simulation, List deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null) { _simulation = simulation; _deferredActions = deferredActions; _simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // ✅ NUEVO: Calcular y almacenar el centro real de la curva var curveSize = outerRadius * 2f; var zPosition = zPos_Curve; _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(topLeft, curveSize, curveSize, 0f, zPosition); // ✅ SIMPLIFICADO: Crear la curva directamente Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0); } // ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático public void SetSpeed(float speed) { Speed = speed; ActualizarVelocidadCinematica(); } /// /// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU. /// public void ActualizarVelocidadCinematica() { if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle)) { var body = _simulation.Bodies.GetBodyReference(BodyHandle); float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f; if (effectiveRadius > 0.001f) { // La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r) // ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius; // La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU body.Velocity.Angular = new Vector3(0, 0, angularSpeed); } else { body.Velocity.Angular = Vector3.Zero; } } } /// /// ✅ NUEVO: Detiene completamente la curva /// public void StopCurve() { SetSpeed(0f); } /// /// ✅ NUEVO: Invierte la dirección de la curva manteniendo la misma velocidad /// public void ReverseCurve() { SetSpeed(-Speed); } /// /// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF /// internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float innerRadius, float outerRadius, float startAngle, float endAngle) { // Actualizar parámetros de la curva _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // ✅ NUEVO: Actualizar el centro real de la curva var curveSize = outerRadius * 2f; var zPosition = zPos_Curve; _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(_curveCenter); } /// /// ✅ NUEVO: Actualiza posición desde Top-Left WPF manteniendo parámetros actuales /// internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft) { var curveSize = _outerRadius * 2f; var zPosition = GetPosition().Z; // Mantener Z actual _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(_curveCenter); } /// /// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual /// internal Vector2 GetWpfTopLeft() { var curveSize = _outerRadius * 2f; return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(_curveCenter, curveSize, curveSize, 0f); } /// /// ✅ CORREGIDO: Obtiene los triángulos reales creados para la curva /// Devuelve los triángulos en coordenadas locales para evitar transformación duplicada /// public Triangle[] GetRealBEPUTriangles() { try { if (_storedTriangles == null || _storedTriangles.Length == 0) { System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] No hay triángulos almacenados"); return new Triangle[0]; } // ✅ CORREGIDO: Devolver triángulos en coordenadas locales // La visualización 3D aplicará la transformación una sola vez System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {_storedTriangles.Length} triángulos en coordenadas locales"); return _storedTriangles; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Error: {ex.Message}"); return new Triangle[0]; } } /// /// ✅ NUEVO: Obtiene los triángulos transformados a coordenadas mundiales (solo para debugging) /// public Triangle[] GetWorldBEPUTriangles() { try { if (_storedTriangles == null || _storedTriangles.Length == 0) { return new Triangle[0]; } var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles); System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos en coordenadas mundiales"); return worldTriangles; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Error: {ex.Message}"); return new Triangle[0]; } } /// /// ✅ NUEVO: Transforma triángulos de coordenadas locales a mundiales /// private Triangle[] TransformTrianglesToWorldCoordinates(Triangle[] localTriangles) { try { if (_simulation == null || !_simulation.Bodies.BodyExists(BodyHandle)) { System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Cuerpo no existe, devolviendo triángulos locales"); return localTriangles; // Fallback: devolver triángulos sin transformar } var body = _simulation.Bodies[BodyHandle]; var bodyPosition = body.Pose.Position; var bodyOrientation = body.Pose.Orientation; var transformedTriangles = new Triangle[localTriangles.Length]; for (int i = 0; i < localTriangles.Length; i++) { var localTriangle = localTriangles[i]; // Transformar cada vértice del triángulo a coordenadas mundiales var worldA = bodyPosition + Vector3.Transform(localTriangle.A, bodyOrientation); var worldB = bodyPosition + Vector3.Transform(localTriangle.B, bodyOrientation); var worldC = bodyPosition + Vector3.Transform(localTriangle.C, bodyOrientation); transformedTriangles[i] = new Triangle(worldA, worldB, worldC); } System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Transformados {transformedTriangles.Length} triángulos. Body pos: {bodyPosition}"); return transformedTriangles; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Error: {ex.Message}, devolviendo triángulos locales"); return localTriangles; // Fallback en caso de error } } public new void RemoverBody() { // ✅ NUEVO: Limpiar triángulos almacenados _storedTriangles = null; // ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único) base.RemoverBody(); } public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 wpfTopLeft, float unused = 0) { // ✅ CORREGIDO: Como Aether - startAngle y endAngle definen directamente el sector // No hay rotación separada del objeto // Actualizar parámetros internos _innerRadius = innerRadius; _outerRadius = outerRadius; // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); // ✅ NUEVO: Actualizar el centro real de la curva var curveSize = outerRadius * 2f; var zPosition = zPos_Curve; _curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition); Create(_curveCenter); // Llamar a la sobrecarga privada } private void Create(Vector3 position) { RemoverBody(); var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle); if (triangles.Length == 0) { System.Diagnostics.Debug.WriteLine($"[simCurve] No se crearon triángulos, no se creará el cuerpo."); return; } _storedTriangles = triangles; // ✅ CORREGIDO: Convertir el array de triángulos a un Buffer de BEPU. _simulation.BufferPool.Take(triangles.Length, out BepuUtilities.Memory.Buffer triangleBuffer); for (int i = 0; i < triangles.Length; i++) { triangleBuffer[i] = triangles[i]; } var mesh = new Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool); var shapeIndex = _simulation.Shapes.Add(mesh); var bodyDescription = BodyDescription.CreateKinematic( new RigidPose(position, Quaternion.Identity), // La rotación se maneja con la velocidad angular, no con la pose inicial new CollidableDescription(shapeIndex, 0.1f), new BodyActivityDescription(-1f) // ✅ CORREGIDO: -1f para que el cuerpo cinemático NUNCA duerma y no se mueva por su cuenta. ); BodyHandle = _simulation.Bodies.Add(bodyDescription); _bodyCreated = true; // ✅ NUEVO: Establecer la velocidad cinemática inicial al crear el cuerpo. ActualizarVelocidadCinematica(); } /// /// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente /// Solo superficie superior, eliminando complejidad innecesaria /// Los triángulos se crean en coordenadas locales (Z=0) y la posición del cuerpo los ubica correctamente /// /// Radio interno del arco /// Radio externo del arco /// Ángulo inicial en radianes BEPU /// Ángulo final en radianes BEPU /// Array de triángulos nativos de BEPU en coordenadas locales private Triangle[] CreateSimpleArcTriangles(float innerRadius, float outerRadius, float startAngle, float endAngle) { var triangles = new List(); // ✅ SIMPLIFICADO: Menos segmentos, menos complejidad float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos float angleStep = (endAngle - startAngle) / segments; // ✅ SIMPLIFICADO: Sin inversión compleja de ángulos for (int i = 0; i < segments; i++) { float angle1 = startAngle + i * angleStep; float angle2 = startAngle + (i + 1) * angleStep; // ✅ COORDENADAS LOCALES: Triángulos en Z=0, el cuerpo los posiciona correctamente var inner1 = new Vector3(innerRadius * (float)Math.Cos(angle1), innerRadius * (float)Math.Sin(angle1), 0); var outer1 = new Vector3(outerRadius * (float)Math.Cos(angle1), outerRadius * (float)Math.Sin(angle1), 0); var inner2 = new Vector3(innerRadius * (float)Math.Cos(angle2), innerRadius * (float)Math.Sin(angle2), 0); var outer2 = new Vector3(outerRadius * (float)Math.Cos(angle2), outerRadius * (float)Math.Sin(angle2), 0); // ✅ USAR Triangle NATIVO DE BEPU (solo superficie superior) triangles.Add(new Triangle(inner1, outer1, outer2)); triangles.Add(new Triangle(inner1, outer2, inner2)); } System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales"); return triangles.ToArray(); } } }