Implementación de un sistema simplificado para la gestión de motores en simCurve, unificando métodos y eliminando redundancias. Se introdujeron nuevas propiedades para el manejo de motores lineales y angulares, y se mejoró la creación de triángulos en la simulación. Además, se optimizó la visualización 3D extrayendo triángulos reales de BEPU y se añadieron métodos para depuración y verificación de triángulos válidos. Se eliminaron métodos y propiedades obsoletas, mejorando la eficiencia del código.

This commit is contained in:
Miguel 2025-07-03 01:47:46 +02:00
parent 4ff93a5802
commit 501f0ffb9b
2 changed files with 458 additions and 481 deletions

View File

@ -859,7 +859,9 @@ namespace CtrEditor.Simulacion
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
// ✅ NUEVAS PROPIEDADES - gestión de motores
// ✅ NUEVO SISTEMA SIMPLIFICADO: Un solo motor por botella
public enum MotorType { None, Linear, Angular }
public MotorType CurrentMotorType { get; set; } = MotorType.None;
public simTransporte CurrentTransport { get; set; }
public ConstraintHandle CurrentMotor { get; set; }
public bool HasActiveMotor => CurrentMotor.Value != 0;
@ -1066,11 +1068,13 @@ namespace CtrEditor.Simulacion
return isOnTransports > 0;
}
// ✅ NUEVOS MÉTODOS - gestión de estado
public void AssignToTransport(simTransporte transport, ConstraintHandle motor)
// ✅ SISTEMA SIMPLIFICADO: Métodos unificados de gestión de motores
public void AssignLinearMotor(simTransporte transport, ConstraintHandle motor)
{
CurrentTransport = transport;
RemoveCurrentMotor(); // Eliminar motor anterior automáticamente
CurrentMotor = motor;
CurrentMotorType = MotorType.Linear;
CurrentTransport = transport;
if (transport.isBrake)
{
isOnBrakeTransport = true;
@ -1078,12 +1082,36 @@ namespace CtrEditor.Simulacion
}
}
public void RemoveFromTransport()
public void AssignAngularMotor(simCurve curve, ConstraintHandle motor)
{
CurrentTransport = null;
RemoveCurrentMotor(); // Eliminar motor anterior automáticamente
CurrentMotor = motor;
CurrentMotorType = MotorType.Angular;
// Las curvas no usan CurrentTransport
}
public void RemoveCurrentMotor()
{
if (HasActiveMotor)
{
// El MotorManager se encargará de eliminar el motor del solver
CurrentMotor = default;
CurrentMotorType = MotorType.None;
CurrentTransport = null;
isOnBrakeTransport = false;
CurrentBrakeTransport = null;
}
}
// ✅ LEGACY: Mantener compatibilidad con código existente
public void AssignToTransport(simTransporte transport, ConstraintHandle motor)
{
AssignLinearMotor(transport, motor);
}
public void RemoveFromTransport()
{
RemoveCurrentMotor();
}
}
@ -1104,54 +1132,43 @@ namespace CtrEditor.Simulacion
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
private List<Action> _deferredActions;
internal List<BodyHandle> _triangleBodyHandles; // Lista de todos los triángulos que componen la curva
private List<List<Vector3>> _originalTriangles; // ✅ NUEVO: Triángulos originales para visualización debug
// ✅ SIMPLIFICADO: Solo necesitamos velocidad en m/s, no sistema complejo de direcciones
public float SpeedMetersPerSecond { get; private set; }
// ✅ SIMPLIFICADO: Propiedades esenciales únicamente
public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>();
// ✅ NUEVO EVENTO - para actualización de motores
// ✅ EVENTO para actualización de motores
public event Action<simCurve> OnSpeedChanged;
// ✅ NUEVA REFERENCIA - para limpiar motors
// ✅ REFERENCIA para limpiar motors
private SimulationManagerBEPU _simulationManager;
// ✅ NUEVO: Direcciones tangenciales precalculadas para cada triángulo
internal Dictionary<BodyHandle, Vector3> _triangleDirections = new Dictionary<BodyHandle, Vector3>();
// ✅ 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
/// <summary>
/// ✅ NUEVO: Expone los triángulos originales para visualización debug
/// </summary>
public List<List<Vector3>> GetOriginalTriangles() => _originalTriangles ?? new List<List<Vector3>>();
public simCurve(Simulation simulation, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null)
{
_simulation = simulation;
_deferredActions = deferredActions;
_simulationManager = simulationManager; // ✅ NUEVA REFERENCIA
_simulationManager = simulationManager;
_innerRadius = innerRadius;
_outerRadius = outerRadius;
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
// ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
_triangleBodyHandles = new List<BodyHandle>();
_originalTriangles = new List<List<Vector3>>(); // ✅ NUEVO: Inicializar lista de triángulos
// Crear la curva con los ángulos que definen el sector
// ✅ SIMPLIFICADO: Crear la curva directamente
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
}
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
public void SetSpeed(float speed)
{
Speed = -speed;
SpeedMetersPerSecond = Math.Abs(Speed) / simBase.SPEED_CONVERSION_FACTOR;
Speed = speed; // Velocidad angular directa (sin inversión)
OnSpeedChanged?.Invoke(this);
}
@ -1210,34 +1227,159 @@ namespace CtrEditor.Simulacion
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, curveSize, curveSize, 0f);
}
public new void RemoverBody()
/// <summary>
/// ✅ NUEVO: Obtiene los triángulos reales creados para la curva
/// Devuelve los triángulos transformados a coordenadas mundiales usando la posición del cuerpo
/// </summary>
public Triangle[] GetRealBEPUTriangles()
{
// ✅ CRÍTICO: Limpiar todos los motors conectados a esta curva ANTES de eliminar los bodies
RemoveConnectedMotors();
// ✅ NUEVO: Limpiar cachés de direcciones
_triangleDirections.Clear();
// Remover todos los triángulos de la curva
if (_triangleBodyHandles != null && _simulation != null)
try
{
foreach (var triangleHandle in _triangleBodyHandles)
if (_storedTriangles == null || _storedTriangles.Length == 0)
{
if (_simulation.Bodies.BodyExists(triangleHandle))
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] No hay triángulos almacenados");
return new Triangle[0];
}
// ✅ TRANSFORMAR a coordenadas mundiales
var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles);
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos transformados a coordenadas mundiales");
return worldTriangles;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Error: {ex.Message}");
return new Triangle[0];
}
}
/// <summary>
/// ✅ NUEVO: Transforma triángulos de coordenadas locales a mundiales
/// </summary>
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
}
}
/// <summary>
/// ✅ NUEVO: Información de debug sobre el Mesh real de BEPU
/// </summary>
public string GetBEPUMeshDebugInfo()
{
try
{
if (_simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
return "ERROR: Body no existe en simulación";
var body = _simulation.Bodies[BodyHandle];
var collidable = body.Collidable;
var shapeIndex = collidable.Shape;
var info = new System.Text.StringBuilder();
info.AppendLine("=== DEBUG INFO MESH REAL DE BEPU ===");
info.AppendLine($"Position: {body.Pose.Position}");
info.AppendLine($"Orientation: {body.Pose.Orientation}");
info.AppendLine($"Shape Index: {shapeIndex.Index}");
info.AppendLine($"Shape Type: {shapeIndex.Type} (Mesh.Id = {BepuPhysics.Collidables.Mesh.Id})");
info.AppendLine($"Shape exists: {shapeIndex.Exists}");
if (_storedTriangles != null)
{
info.AppendLine($"Triángulos almacenados (locales): {_storedTriangles.Length}");
// Mostrar algunos triángulos locales de ejemplo
int triangleCount = Math.Min(3, _storedTriangles.Length);
info.AppendLine($"");
info.AppendLine($"Primeros {triangleCount} triángulos LOCALES:");
for (int i = 0; i < triangleCount; i++)
{
_simulation.Bodies.Remove(triangleHandle);
var triangle = _storedTriangles[i];
info.AppendLine($" Triángulo LOCAL {i}:");
info.AppendLine($" A: ({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})");
info.AppendLine($" B: ({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})");
info.AppendLine($" C: ({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})");
}
// ✅ NUEVO: Mostrar triángulos transformados a coordenadas mundiales
try
{
var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles);
info.AppendLine($"");
info.AppendLine($"Primeros {triangleCount} triángulos MUNDIALES (transformados):");
for (int i = 0; i < Math.Min(triangleCount, worldTriangles.Length); i++)
{
var triangle = worldTriangles[i];
info.AppendLine($" Triángulo MUNDIAL {i}:");
info.AppendLine($" A: ({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})");
info.AppendLine($" B: ({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})");
info.AppendLine($" C: ({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})");
}
}
catch (Exception ex)
{
info.AppendLine($"ERROR obteniendo triángulos mundiales: {ex.Message}");
}
}
_triangleBodyHandles.Clear();
}
else
{
info.AppendLine($"ERROR: No hay triángulos almacenados");
}
// ✅ NUEVO: Limpiar también los triángulos originales
if (_originalTriangles != null)
return info.ToString();
}
catch (Exception ex)
{
_originalTriangles.Clear();
return $"ERROR obteniendo debug info: {ex.Message}";
}
}
// Remover el cuerpo principal (si existe)
public new void RemoverBody()
{
// ✅ SIMPLIFICADO: Limpiar motors conectados
RemoveConnectedMotors();
// ✅ NUEVO: Limpiar triángulos almacenados
_storedTriangles = null;
// ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único)
base.RemoverBody();
}
@ -1251,17 +1393,8 @@ namespace CtrEditor.Simulacion
// ✅ USAR REFERENCIA DIRECTA al SimulationManager
if (_simulationManager != null)
{
// Limpiar motors del cuerpo principal
// ✅ SIMPLIFICADO: Solo limpiar motors del cuerpo principal
_simulationManager.RemoveMotorsConnectedToBody(BodyHandle);
// Limpiar motors de todos los triángulos
if (_triangleBodyHandles != null)
{
foreach (var triangleHandle in _triangleBodyHandles)
{
_simulationManager.RemoveMotorsConnectedToBody(triangleHandle);
}
}
}
// Limpiar la lista local de botellas
@ -1300,237 +1433,84 @@ namespace CtrEditor.Simulacion
{
RemoverBody();
// Crear triángulos para la curva - los ángulos ya definen la forma correcta
var triangles = CreateTriangulatedCurve(_innerRadius, _outerRadius, -_startAngle, -_endAngle);
// ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
// ✅ NUEVO: Almacenar triángulos originales para visualización debug
_originalTriangles = new List<List<Vector3>>(triangles);
// ✅ ALMACENAR triángulos para acceso directo
_storedTriangles = triangles;
foreach (var triangle in triangles)
// ✅ SIMPLIFICADO: Crear un solo cuerpo con múltiples Triangle shapes usando Mesh
if (triangles.Length > 0)
{
CreateTriangleBody(triangle, position);
}
// Crear un cuerpo principal invisible para referencia de posición
CreateMainBody(position);
}
private void CreateMainBody(Vector3 position)
{
// Crear un cuerpo principal muy pequeño e invisible solo para referencia
var smallBox = new Box(0.01f, 0.01f, 0.01f);
var shapeIndex = _simulation.Shapes.Add(smallBox);
// ✅ CREAR MESH CON LA API CORRECTA DE BEPU
var triangleBuffer = new BepuUtilities.Memory.Buffer<Triangle>(triangles.Length, _simulation.BufferPool);
for (int i = 0; i < triangles.Length; i++)
{
triangleBuffer[i] = triangles[i];
}
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
var shapeIndex = _simulation.Shapes.Add(mesh);
var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(position), // Sin rotación - la forma ya está definida por los ángulos
new CollidableDescription(shapeIndex, 0f), // Sin speculative margin
new RigidPose(position),
new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa
new BodyActivityDescription(0.01f)
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true;
}
private void CreateTriangleBody(List<Vector3> triangle, Vector3 basePosition)
{
if (triangle.Count != 3)
return;
try
{
// Calcular centro y dimensiones del triángulo
var center = (triangle[0] + triangle[1] + triangle[2]) / 3f;
var worldCenter = center + basePosition;
// ✅ NUEVO: Calcular dirección tangencial con compensación centrípeta
// Usamos la posición del centro del triángulo para calcular la tangente
var radiusVector = new Vector2(center.X, center.Y);
if (radiusVector.Length() > 0.001f)
{
// Vector tangente (perpendicular al radio) en el plano XY
var tangent = new Vector3(-radiusVector.Y, radiusVector.X, 0);
tangent = Vector3.Normalize(tangent);
// ✅ COMPENSACIÓN CENTRÍPETA: Inclinar la dirección hacia el centro
var radiusVector3D = new Vector3(radiusVector.X, radiusVector.Y, 0);
var toCenter = -Vector3.Normalize(radiusVector3D); // Vector hacia el centro
// Mezclar tangente con componente centrípeta (10% hacia el centro)
const float CENTRIPETAL_FACTOR = 0.1f; // 10% de fuerza hacia el centro
var adjustedDirection = Vector3.Normalize(tangent + toCenter * CENTRIPETAL_FACTOR);
// Ajustar dirección según la velocidad (signo)
if (Speed < 0)
adjustedDirection = -adjustedDirection;
// Almacenar dirección ajustada antes de crear el body
var triangleDirection = adjustedDirection;
// Calcular dimensiones aproximadas del triángulo para crear una caja plana
float minX = Math.Min(Math.Min(triangle[0].X, triangle[1].X), triangle[2].X);
float maxX = Math.Max(Math.Max(triangle[0].X, triangle[1].X), triangle[2].X);
float minY = Math.Min(Math.Min(triangle[0].Y, triangle[1].Y), triangle[2].Y);
float maxY = Math.Max(Math.Max(triangle[0].Y, triangle[1].Y), triangle[2].Y);
float width = Math.Max(maxX - minX, 0.01f);
float height = Math.Max(maxY - minY, 0.01f);
float thickness = zAltura_Curve; // Altura muy pequeña para triángulos planos
// Crear una caja plana que aproxime el triángulo
var box = new Box(width, height, thickness);
var shapeIndex = _simulation.Shapes.Add(box);
// Crear como cuerpo estático sólido (NO sensor)
var activityDescription = new BodyActivityDescription(0.01f);
var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(worldCenter),
new CollidableDescription(shapeIndex, 0.001f), // Speculative margin normal
activityDescription
);
var triangleHandle = _simulation.Bodies.Add(bodyDescription);
_triangleBodyHandles.Add(triangleHandle);
// ✅ NUEVO: Almacenar la dirección tangencial para este triángulo
_triangleDirections[triangleHandle] = triangleDirection;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[simCurve] Error creating triangle: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados");
}
}
private const float SegmentationFactor = 32f / 3f;
private const int MinSegments = 8;
private const int MaxSegments = 64;
// ✅ MÉTODOS ELIMINADOS: CreateMainBody y CreateTriangleBody ya no son necesarios
// La curva ahora se crea como un solo Mesh en el método Create simplificado
/// <summary>
/// Crea triángulos para representar una curva/arco en la simulación BEPU.
/// IMPORTANTE: Los parámetros startAngle y endAngle ya están convertidos a radianes BEPU internamente,
/// pero además se invierte la dirección del arco para mantener coherencia visual con WPF.
/// ✅ 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
/// </summary>
/// <param name="innerRadius">Radio interno del arco</param>
/// <param name="outerRadius">Radio externo del arco</param>
/// <param name="startAngle">Ángulo inicial en radianes BEPU (ya convertido desde grados WPF)</param>
/// <param name="endAngle">Ángulo final en radianes BEPU (ya convertido desde grados WPF)</param>
/// <returns>Lista de triángulos que forman el arco</returns>
private List<List<Vector3>> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle)
/// <param name="startAngle">Ángulo inicial en radianes BEPU</param>
/// <param name="endAngle">Ángulo final en radianes BEPU</param>
/// <returns>Array de triángulos nativos de BEPU en coordenadas locales</returns>
private Triangle[] CreateSimpleArcTriangles(float innerRadius, float outerRadius, float startAngle, float endAngle)
{
var triangles = new List<List<Vector3>>();
var triangles = new List<Triangle>();
// Calcular número de segmentos basado en el tamaño del arco
// ✅ SIMPLIFICADO: Menos segmentos, menos complejidad
float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
int segments = (int)(arcLength * SegmentationFactor);
segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments));
int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos
float angleStep = (endAngle - startAngle) / segments;
// ✅ CRÍTICO: Inversión de dirección del arco para conversión WPF ↔ BEPU
//
// Los ángulos individuales ya están convertidos (WPF grados → BEPU radianes con signo invertido),
// pero también necesitamos invertir la DIRECCIÓN del arco:
//
// • WPF: startAngle → endAngle (sentido horario, Y+ hacia abajo)
// • BEPU: -endAngle → -startAngle (sentido antihorario, Y+ hacia arriba)
//
// Ejemplo: WPF arco de 0° a 90° → BEPU arco de -90° a 0° (equivalente visual)
float fromAngle = -endAngle; // Empezar desde el ángulo final negativo
float toAngle = -startAngle; // Terminar en el ángulo inicial negativo
float angleStep = (toAngle - fromAngle) / segments;
// Generar vértices para los arcos interior y exterior
var innerPoints = new Vector3[segments + 1];
var outerPoints = new Vector3[segments + 1];
for (int i = 0; i <= segments; i++)
{
float angle = fromAngle + i * angleStep;
float cosAngle = (float)Math.Cos(angle);
float sinAngle = (float)Math.Sin(angle);
// Triángulos planos en el plano XY (Z = 0 relativo)
innerPoints[i] = new Vector3(innerRadius * cosAngle, innerRadius * sinAngle, 0);
outerPoints[i] = new Vector3(outerRadius * cosAngle, outerRadius * sinAngle, 0);
}
// Crear triángulos
// ✅ SIMPLIFICADO: Sin inversión compleja de ángulos
for (int i = 0; i < segments; i++)
{
// Primer triángulo del sector
var upperTriangle = new List<Vector3>
{
innerPoints[i],
outerPoints[i],
outerPoints[i + 1]
};
triangles.Add(upperTriangle);
// Segundo triángulo del sector
var lowerTriangle = new List<Vector3>
{
innerPoints[i],
outerPoints[i + 1],
innerPoints[i + 1]
};
triangles.Add(lowerTriangle);
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));
}
return triangles;
System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales");
return triangles.ToArray();
}
/// <summary>
/// Calcula la dirección tangencial para aplicar fuerzas de curva.
/// NOTA: Este cálculo es dinámico (basado en posición actual) y no se ve afectado
/// por la inversión de dirección del arco en CreateTriangulatedCurve.
/// </summary>
public Vector3 GetTangentialDirection(Vector3 bottlePosition)
{
try
{
var curvePosition = GetPosition();
var centerToBottle = new Vector2(bottlePosition.X - curvePosition.X, bottlePosition.Y - curvePosition.Y);
if (centerToBottle.Length() < 0.001f)
return Vector3.Zero;
// Vector tangente (perpendicular al radio) en el plano XY
var tangent = new Vector3(-centerToBottle.Y, centerToBottle.X, 0);
// Normalizar
if (tangent.Length() > 0.001f)
{
tangent = Vector3.Normalize(tangent);
}
// Ajustar dirección según la velocidad (signo)
if (Speed < 0)
tangent = -tangent;
return tangent;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[simCurve] Error calculating tangential direction: {ex.Message}");
return Vector3.Zero;
}
}
// ✅ MÉTODO MEJORADO - versión optimizada
public Vector3 GetCachedTangentialDirection(Vector3 bottlePosition)
{
var direction = GetTangentialDirection(bottlePosition);
return Speed < 0 ? -direction : direction;
}
/// <summary>
/// ✅ NUEVO - Obtiene la dirección tangencial fija de un triángulo específico
/// </summary>
public Vector3 GetTriangleDirection(BodyHandle triangleHandle)
{
return _triangleDirections.GetValueOrDefault(triangleHandle, Vector3.Zero);
}
// ✅ MÉTODOS ELIMINADOS: Los cálculos tangenciales complejos ya no son necesarios
// AngularAxisMotor maneja automáticamente la rotación en curvas
}
public class simDescarte : simBase
@ -1712,11 +1692,11 @@ namespace CtrEditor.Simulacion
return false; // NO generar contacto físico para descartes
}
// ✅ PROCESAR CREACIÓN DE MOTORES para transportes y curvas
// ✅ SIMPLIFICADO: Detección directa de transportes y curvas
var transportA = GetTransportFromCollidable(pair.A);
var transportB = GetTransportFromCollidable(pair.B);
(simCurve curveA, Vector3 directionA) = GetCurveAndDirectionFromCollidable(pair.A);
(simCurve curveB, Vector3 directionB) = GetCurveAndDirectionFromCollidable(pair.B);
var curveA = GetCurveFromCollidable(pair.A);
var curveB = GetCurveFromCollidable(pair.B);
// ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS
BodyHandle? bottleHandle = null;
@ -1741,23 +1721,21 @@ namespace CtrEditor.Simulacion
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
}
// ✅ SIMPLIFICADO: Si hay contacto botella-triángulo de curva, crear motor con dirección específica
// ✅ SIMPLIFICADO: Si hay contacto botella-curva, crear AngularAxisMotor
else if (botella != null && (curveA != null || curveB != null))
{
var curve = curveA ?? curveB;
var direction = curveA != null ? directionA : directionB;
var triangleHandle = curveA != null ? pair.A.BodyHandle : pair.B.BodyHandle;
bottleHandle = botella.BodyHandle;
elementHandle = triangleHandle;
elementHandle = curve.BodyHandle;
// ✅ SIMPLIFICADO: Crear motor usando sistema natural de colisiones de BEPU
// ✅ SIMPLIFICADO: Crear motor angular usando el cuerpo principal de la curva
var pairKey = (bottleHandle.Value, elementHandle.Value);
if (!_simulationManager._motorsCreated.Contains(pairKey))
{
_simulationManager.TryCreateCurveMotor(botella, curve, direction, triangleHandle);
_simulationManager.TryCreateCurveAngularMotor(botella, curve);
_simulationManager._motorsCreated.Add(pairKey);
System.Diagnostics.Debug.WriteLine($"[Manifold] Motor curva: {bottleHandle} → {triangleHandle}");
System.Diagnostics.Debug.WriteLine($"[Manifold] Motor angular curva: {bottleHandle} → {elementHandle}");
}
// Fricción alta para curvas (SIEMPRE aplicar para colisión física)
@ -1842,37 +1820,12 @@ namespace CtrEditor.Simulacion
if (collidable.Mobility == CollidableMobility.Kinematic)
{
var bodyHandle = collidable.BodyHandle;
// ✅ NUEVO: Buscar específicamente en los triángulos de las curvas
return _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle);
var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle);
return simBase as simCurve;
}
}
return null;
}
/// <summary>
/// ✅ NUEVO: Obtiene curva y dirección específica del triángulo
/// </summary>
private (simCurve curve, Vector3 direction) GetCurveAndDirectionFromCollidable(CollidableReference collidable)
{
if (_simulationManager?.simulation != null)
{
if (collidable.Mobility == CollidableMobility.Kinematic)
{
var bodyHandle = collidable.BodyHandle;
// Buscar curva que contenga este triángulo
var curve = _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle);
if (curve != null)
{
// Obtener dirección específica del triángulo
var direction = curve.GetTriangleDirection(bodyHandle);
return (curve, direction);
}
}
}
return (null, Vector3.Zero);
}
/// <summary>
/// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold
@ -2013,6 +1966,15 @@ namespace CtrEditor.Simulacion
}
public void CreateCurveMotor(simBotella bottle, simCurve curve)
{
// ✅ SIMPLIFICADO: Usar AngularAxisMotor para curvas
CreateCurveAngularMotor(bottle, curve);
}
/// <summary>
/// ✅ NUEVO: Crear motor angular para curvas (simplificado)
/// </summary>
public void CreateCurveAngularMotor(simBotella bottle, simCurve curve)
{
try
{
@ -2025,50 +1987,34 @@ namespace CtrEditor.Simulacion
!_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
return;
var direction = curve.GetCachedTangentialDirection(bottle.GetPosition());
// Validar que la dirección no sea cero
if (direction.Length() < 0.001f)
// Verificar que la curva tenga velocidad
if (Math.Abs(curve.Speed) < 0.001f)
return;
// ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA
Vector3 localAxisDirection;
if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
// ✅ SIMPLIFICADO: AngularAxisMotor para rotación en el plano XY
var angularMotor = new AngularAxisMotor()
{
var curveBody = _simulationManager.simulation.Bodies[curve.BodyHandle];
var inverseCurveOrientation = Quaternion.Conjugate(curveBody.Pose.Orientation);
localAxisDirection = Vector3.Transform(direction, inverseCurveOrientation);
}
else
{
// Fallback: usar dirección mundial si no se puede obtener orientación
localAxisDirection = direction;
}
var motor = new LinearAxisMotor()
{
LocalOffsetA = Vector3.Zero,
LocalOffsetB = Vector3.Zero,
LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales
TargetVelocity = curve.SpeedMetersPerSecond,
Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f)
LocalAxisA = Vector3.UnitZ, // Eje Z para rotación en plano XY
TargetVelocity = curve.Speed, // Velocidad angular directa en rad/s
Settings = new MotorSettings(Math.Max(bottle.Mass * 10f, 5f), 3f)
};
var motorHandle = _simulationManager.simulation.Solver.Add(
curve.BodyHandle, // ✅ USAR CUERPO DE LA CURVA
curve.BodyHandle,
bottle.BodyHandle,
motor
angularMotor
);
_activeMotors[bottle.BodyHandle] = motorHandle;
_motorToBottle[motorHandle] = bottle;
bottle.CurrentMotor = motorHandle;
bottle.AssignAngularMotor(curve, motorHandle);
curve.BottlesOnCurve.Add(bottle);
System.Diagnostics.Debug.WriteLine($"[MotorManager] Angular motor created: {bottle.BodyHandle} → {curve.BodyHandle}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating curve motor: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating angular motor: {ex.Message}");
}
}
@ -2232,7 +2178,7 @@ namespace CtrEditor.Simulacion
LocalOffsetA = Vector3.Zero,
LocalOffsetB = Vector3.Zero,
LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales
TargetVelocity = curve.SpeedMetersPerSecond,
TargetVelocity = curve.Speed, // ✅ SIMPLIFICADO: Usar velocidad directa
Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f)
};
@ -2242,7 +2188,7 @@ namespace CtrEditor.Simulacion
motor
);
// Logging con coordenadas mundiales y locales para depuración
System.Diagnostics.Debug.WriteLine($"[CreateCurveMotorWithDirection] Bottle: {bottle.BodyHandle} World: ({direction.X:F2}, {direction.Y:F2}) Local: ({localAxisDirection.X:F2}, {localAxisDirection.Y:F2}) Speed: {curve.SpeedMetersPerSecond}");
System.Diagnostics.Debug.WriteLine($"[CreateCurveMotorWithDirection] Bottle: {bottle.BodyHandle} World: ({direction.X:F2}, {direction.Y:F2}) Local: ({localAxisDirection.X:F2}, {localAxisDirection.Y:F2}) Speed: {curve.Speed}");
_activeMotors[bottle.BodyHandle] = motorHandle;
_motorToBottle[motorHandle] = bottle;
@ -2371,16 +2317,10 @@ namespace CtrEditor.Simulacion
_activeMotors.Clear();
_motorToBottle.Clear();
// ✅ NUEVO: Limpiar cachés de direcciones de todas las curvas y pares silenciados
// ✅ SIMPLIFICADO: Solo limpiar pares de motores creados
if (_simulationManager?.Cuerpos != null)
{
var curves = _simulationManager.Cuerpos.OfType<simCurve>();
foreach (var curve in curves)
{
curve._triangleDirections?.Clear();
}
// También limpiar pares de motores creados
// Limpiar pares de motores creados
_simulationManager._motorsCreated?.Clear();
}
}
@ -2442,22 +2382,11 @@ namespace CtrEditor.Simulacion
}
/// <summary>
/// ✅ NUEVO: Obtiene una simCurve si el BodyHandle pertenece a uno de sus triángulos
/// ✅ SIMPLIFICADO: Obtiene una simCurve desde su BodyHandle principal
/// </summary>
public simCurve GetCurveFromTriangleBodyHandle(BodyHandle bodyHandle)
{
// Buscar en todas las curvas si alguna contiene este BodyHandle en sus triángulos
var curves = Cuerpos.OfType<simCurve>();
foreach (var curve in curves)
{
if (curve._triangleBodyHandles != null && curve._triangleBodyHandles.Contains(bodyHandle))
{
return curve;
}
}
// Si no se encuentra en triángulos, buscar en cuerpos principales como fallback
// ✅ SIMPLIFICADO: Solo buscar en cuerpos principales (ya no hay triángulos separados)
var simBase = GetSimBaseFromBodyHandle(bodyHandle);
return simBase as simCurve;
}
@ -2479,14 +2408,7 @@ namespace CtrEditor.Simulacion
break;
case simCurve curve:
_curveHandles.Add(curve.BodyHandle);
// También añadir todos sus triángulos
if (curve._triangleBodyHandles != null)
{
foreach (var triangleHandle in curve._triangleBodyHandles)
{
_curveHandles.Add(triangleHandle);
}
}
// ✅ SIMPLIFICADO: Ya no hay triángulos separados, solo el cuerpo principal
curve.OnSpeedChanged += _motorManager.UpdateCurveSpeed;
break;
case simBarrera barrier:
@ -2515,13 +2437,7 @@ namespace CtrEditor.Simulacion
break;
case simCurve curve:
_curveHandles.Remove(curve.BodyHandle);
if (curve._triangleBodyHandles != null)
{
foreach (var triangleHandle in curve._triangleBodyHandles)
{
_curveHandles.Remove(triangleHandle);
}
}
// ✅ SIMPLIFICADO: Ya no hay triángulos separados
curve.OnSpeedChanged -= _motorManager.UpdateCurveSpeed;
break;
case simBarrera barrier:
@ -2689,7 +2605,7 @@ namespace CtrEditor.Simulacion
foreach (var curve in Cuerpos.OfType<simCurve>())
{
// ✅ CORREGIDO: Usar método público para actualizar velocidad
curve.SetSpeed(curve.Speed); // Esto actualiza automáticamente SpeedMetersPerSecond internamente
curve.SetSpeed(curve.Speed); // ✅ SIMPLIFICADO: Reinicializar velocidad
}
stopwatch.Start();
@ -3013,6 +2929,35 @@ namespace CtrEditor.Simulacion
}
}
/// <summary>
/// ✅ NUEVO: Intenta crear un motor angular para curvas si no existe uno activo
/// </summary>
public void TryCreateCurveAngularMotor(simBotella bottle, simCurve curve)
{
try
{
// Validaciones básicas
if (bottle == null || curve == null || _motorManager == null)
return;
// Verificar si ya tiene un motor activo
if (bottle.HasActiveMotor)
return;
// Asegurar que la curva tenga velocidad
if (Math.Abs(curve.Speed) < 0.001f)
return; // No crear motor para curvas detenidas
_motorManager.CreateCurveAngularMotor(bottle, curve);
System.Diagnostics.Debug.WriteLine($"[TryCreateCurveAngularMotor] Motor angular creado: {bottle.BodyHandle} - {curve.BodyHandle}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveAngularMotor: {ex.Message}");
}
}
/// <summary>

View File

@ -386,153 +386,130 @@ namespace CtrEditor.Simulacion
}
/// <summary>
/// ✅ NUEVO: Crea mesh directamente desde los triángulos de BEPU (debug real)
/// ✅ NUEVO: Crea mesh directamente desde los triángulos reales de BEPU
/// Extrae la geometría exacta que está almacenada en la simulación física
/// </summary>
private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve)
{
try
{
// Obtener los triángulos originales directamente de BEPU
var originalTriangles = curve.GetOriginalTriangles();
if (originalTriangles.Count == 0)
// ✅ EXTRAER TRIÁNGULOS REALES DE BEPU
var realTriangles = curve.GetRealBEPUTriangles();
if (realTriangles.Length == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
System.Diagnostics.Debug.WriteLine($"[3D BEPU] WARNING: No se pudieron extraer triángulos reales, usando fallback");
CreateCurveMeshFallback(meshBuilder, curve);
return;
}
// Altura para simular triángulos planos (misma que en BEPU)
const float curveHeight = 0.05f; // simCurve.zAltura_Curve
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {realTriangles.Length} triángulos reales de BEPU");
// Convertir cada triángulo de BEPU a triángulos en Helix
foreach (var triangle in originalTriangles)
// ✅ USAR TRIÁNGULOS EXACTOS DE BEPU
foreach (var triangle in realTriangles)
{
if (triangle.Count != 3)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle with {triangle.Count} vertices");
continue;
}
// Convertir Vector3 de BEPU a Point3D de Helix
// Los triángulos en BEPU están en el plano XY (Z=0), crear superficie 3D
// Puntos de la superficie inferior (Z = 0)
var p1Bottom = new Point3D(triangle[0].X, triangle[0].Y, 0);
var p2Bottom = new Point3D(triangle[1].X, triangle[1].Y, 0);
var p3Bottom = new Point3D(triangle[2].X, triangle[2].Y, 0);
// Puntos de la superficie superior (Z = curveHeight)
var p1Top = new Point3D(triangle[0].X, triangle[0].Y, curveHeight);
var p2Top = new Point3D(triangle[1].X, triangle[1].Y, curveHeight);
var p3Top = new Point3D(triangle[2].X, triangle[2].Y, curveHeight);
// Crear superficie superior del triángulo
meshBuilder.AddTriangle(p1Top, p2Top, p3Top);
// Crear superficie inferior del triángulo (orden inverso para normales correctas)
meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom);
// Crear paredes laterales del triángulo (3 quads)
meshBuilder.AddQuad(p1Bottom, p2Bottom, p2Top, p1Top); // Lado 1-2
meshBuilder.AddQuad(p2Bottom, p3Bottom, p3Top, p2Top); // Lado 2-3
meshBuilder.AddQuad(p3Bottom, p1Bottom, p1Top, p3Top); // Lado 3-1
// Convertir triángulos de BEPU a puntos 3D de Helix
var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z);
var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z);
var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z);
// Agregar triángulo exacto al mesh
meshBuilder.AddTriangle(pointA, pointB, pointC);
}
System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos reales de BEPU");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating mesh from BEPU triangles: {ex.Message}");
// Fallback: usar el método anterior si falla la lectura de BEPU
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to recreated geometry");
System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos reales: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Fallback a geometría recreada");
CreateCurveMeshFallback(meshBuilder, curve);
}
}
/// <summary>
/// ✅ NUEVO: Función de debug que muestra triángulos individuales de BEPU
/// Cada triángulo se renderiza de manera separada para poder hacer debug visual
/// ✅ NUEVO: Función de debug que muestra triángulos individuales reales de BEPU
/// Cada triángulo se renderiza de manera separada con offset para poder hacer debug visual
/// </summary>
private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve)
{
try
{
// Obtener los triángulos originales directamente de BEPU
var originalTriangles = curve.GetOriginalTriangles();
if (originalTriangles.Count == 0)
// ✅ EXTRAER TRIÁNGULOS REALES DE BEPU PARA DEBUG
var realTriangles = curve.GetRealBEPUTriangles();
if (realTriangles.Length == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No hay triángulos reales para debug, usando fallback");
CreateCurveMeshFallback(meshBuilder, curve);
return;
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating debug visualization for {originalTriangles.Count} triangles");
System.Diagnostics.Debug.WriteLine($"[3D Debug] Creando debug de {realTriangles.Length} triángulos reales individuales");
// Altura muy pequeña para mostrar triángulos planos (como en BEPU)
const float debugHeight = 0.02f; // Más bajo que la superficie normal para diferenciarlo
const float triangleSeparation = 0.01f; // Separación pequeña entre triángulos para distinguirlos
// Convertir cada triángulo de BEPU a un triángulo 3D individual
for (int i = 0; i < originalTriangles.Count; i++)
// ✅ MOSTRAR CADA TRIÁNGULO SEPARADO CON OFFSET PARA DEBUG
float triangleSeparation = 0.02f; // Separación entre triángulos para debug visual
for (int i = 0; i < realTriangles.Length; i++)
{
var triangle = originalTriangles[i];
if (triangle.Count != 3)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle {i} with {triangle.Count} vertices");
continue;
}
// Calcular el centro del triángulo para aplicar separación
var center = new System.Numerics.Vector3(
(triangle[0].X + triangle[1].X + triangle[2].X) / 3f,
(triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3f,
0
);
// Aplicar una pequeña separación desde el centro para distinguir triángulos
var offset = center * triangleSeparation;
// Crear triángulo superior (visible desde arriba)
var p1Top = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, debugHeight);
var p2Top = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, debugHeight);
var p3Top = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, debugHeight);
// Crear triángulo inferior (visible desde abajo)
var p1Bottom = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, 0);
var p2Bottom = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, 0);
var p3Bottom = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, 0);
// Agregar triángulo superior (normal hacia arriba)
meshBuilder.AddTriangle(p1Top, p2Top, p3Top);
// Agregar triángulo inferior (normal hacia abajo)
meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom);
// ✅ OPCIONAL: Agregar bordes/wireframe para mejor visualización del debug
// Crear líneas delgadas en los bordes del triángulo para verlo mejor
const float edgeThickness = 0.005f;
// Borde 1-2
AddDebugEdge(meshBuilder, p1Top, p2Top, p1Bottom, p2Bottom, edgeThickness);
// Borde 2-3
AddDebugEdge(meshBuilder, p2Top, p3Top, p2Bottom, p3Bottom, edgeThickness);
// Borde 3-1
AddDebugEdge(meshBuilder, p3Top, p1Top, p3Bottom, p1Bottom, edgeThickness);
var triangle = realTriangles[i];
// Calcular centroide del triángulo
var centroid = (triangle.A + triangle.B + triangle.C) / 3f;
// Calcular normal del triángulo
var edge1 = triangle.B - triangle.A;
var edge2 = triangle.C - triangle.A;
var normal = Vector3.Normalize(Vector3.Cross(edge1, edge2));
// Offset hacia arriba para separar triángulos visualmente
var offset = normal * triangleSeparation * (i + 1);
// Aplicar offset a todos los vértices
var offsetA = triangle.A + offset;
var offsetB = triangle.B + offset;
var offsetC = triangle.C + offset;
// Convertir a puntos 3D de Helix
var pointA = new Point3D(offsetA.X, offsetA.Y, offsetA.Z);
var pointB = new Point3D(offsetB.X, offsetB.Y, offsetB.Z);
var pointC = new Point3D(offsetC.X, offsetC.Y, offsetC.Z);
// Agregar triángulo separado
meshBuilder.AddTriangle(pointA, pointB, pointC);
// ✅ DEBUG: Agregar bordes para visualizar mejor cada triángulo
AddDebugTriangleEdges(meshBuilder, pointA, pointB, pointC, 0.005f);
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created debug mesh with {originalTriangles.Count} individual triangles");
System.Diagnostics.Debug.WriteLine($"[3D Debug] ✅ Debug mesh creado con triángulos reales separados");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating debug mesh: {ex.Message}");
// Fallback: mostrar mensaje de error en consola y usar geometría básica
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to basic debug geometry");
CreateBasicDebugGeometry(meshBuilder, curve);
}
}
/// <summary>
/// ✅ NUEVO: Agrega bordes debug a un triángulo individual
/// </summary>
private void AddDebugTriangleEdges(MeshBuilder meshBuilder, Point3D a, Point3D b, Point3D c, double edgeThickness)
{
try
{
// Crear cilindros delgados para los bordes del triángulo
meshBuilder.AddCylinder(a, b, edgeThickness, 4, false, false);
meshBuilder.AddCylinder(b, c, edgeThickness, 4, false, false);
meshBuilder.AddCylinder(c, a, edgeThickness, 4, false, false);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR adding triangle edges: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual
/// </summary>
@ -1071,8 +1048,8 @@ namespace CtrEditor.Simulacion
}
/// <summary>
/// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos de una curva
/// Útil para debug sin cambiar la visualización
/// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos reales de BEPU
/// Extrae y muestra los triángulos exactos almacenados en la simulación física
/// </summary>
/// <param name="curve">Curva para analizar</param>
/// <returns>Información de debug como string</returns>
@ -1083,40 +1060,95 @@ namespace CtrEditor.Simulacion
try
{
var triangles = curve.GetOriginalTriangles();
var debugInfo = new System.Text.StringBuilder();
debugInfo.AppendLine($"=== DEBUG INFO PARA CURVA ===");
debugInfo.AppendLine($"=== DEBUG INFO TRIÁNGULOS REALES DE BEPU ===");
debugInfo.AppendLine($"Parámetros de curva:");
debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}");
debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}");
debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle} rad ({curve.StartAngle * 180 / Math.PI:F1}°)");
debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle} rad ({curve.EndAngle * 180 / Math.PI:F1}°)");
debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle}° ({curve.StartAngle * Math.PI / 180:F3} rad)");
debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle}° ({curve.EndAngle * Math.PI / 180:F3} rad)");
debugInfo.AppendLine($" - Velocidad: {curve.Speed} rad/s");
debugInfo.AppendLine($"");
debugInfo.AppendLine($"Triángulos en BEPU: {triangles.Count}");
for (int i = 0; i < Math.Min(triangles.Count, 10); i++) // Mostrar máximo 10 triángulos
// ✅ OBTENER INFORMACIÓN REAL DEL MESH DE BEPU
var meshInfo = curve.GetBEPUMeshDebugInfo();
debugInfo.AppendLine("INFORMACIÓN DEL MESH EN BEPU:");
debugInfo.AppendLine(meshInfo);
debugInfo.AppendLine($"");
// ✅ EXTRAER Y MOSTRAR TRIÁNGULOS REALES
var realTriangles = curve.GetRealBEPUTriangles();
debugInfo.AppendLine($"TRIÁNGULOS REALES EXTRAÍDOS: {realTriangles.Length}");
debugInfo.AppendLine($"");
// Mostrar los primeros triángulos con detalles completos
int maxToShow = Math.Min(10, realTriangles.Length);
for (int i = 0; i < maxToShow; i++)
{
var triangle = triangles[i];
debugInfo.AppendLine($" Triángulo {i + 1}:");
debugInfo.AppendLine($" P1: ({triangle[0].X:F3}, {triangle[0].Y:F3}, {triangle[0].Z:F3})");
debugInfo.AppendLine($" P2: ({triangle[1].X:F3}, {triangle[1].Y:F3}, {triangle[1].Z:F3})");
debugInfo.AppendLine($" P3: ({triangle[2].X:F3}, {triangle[2].Y:F3}, {triangle[2].Z:F3})");
var triangle = realTriangles[i];
debugInfo.AppendLine($"Triángulo {i + 1}:");
debugInfo.AppendLine($" A: ({triangle.A.X:F4}, {triangle.A.Y:F4}, {triangle.A.Z:F4})");
debugInfo.AppendLine($" B: ({triangle.B.X:F4}, {triangle.B.Y:F4}, {triangle.B.Z:F4})");
debugInfo.AppendLine($" C: ({triangle.C.X:F4}, {triangle.C.Y:F4}, {triangle.C.Z:F4})");
// Calcular área del triángulo
var edge1 = triangle.B - triangle.A;
var edge2 = triangle.C - triangle.A;
var cross = Vector3.Cross(edge1, edge2);
var area = cross.Length() / 2f;
debugInfo.AppendLine($" Área: {area:F6}");
debugInfo.AppendLine($"");
}
if (triangles.Count > 10)
if (realTriangles.Length > maxToShow)
{
debugInfo.AppendLine($" ... y {triangles.Count - 10} triángulos más");
debugInfo.AppendLine($"... y {realTriangles.Length - maxToShow} triángulos más");
}
debugInfo.AppendLine($"");
debugInfo.AppendLine($"Modo debug actual: {(DebugShowIndividualTriangles ? "ACTIVADO" : "DESACTIVADO")}");
debugInfo.AppendLine($"Modo debug visual: {(DebugShowIndividualTriangles ? "ACTIVADO (triángulos separados)" : "DESACTIVADO (superficie continua)")}");
return debugInfo.ToString();
}
catch (Exception ex)
{
return $"ERROR obteniendo info de debug: {ex.Message}";
return $"ERROR obteniendo info de debug real: {ex.Message}";
}
}
/// <summary>
/// ✅ NUEVO: Activa la visualización de triángulos reales de BEPU
/// Muestra los triángulos exactos extraídos de la simulación física
/// </summary>
/// <param name="enable">True para mostrar triángulos reales, false para superficie normal</param>
public void SetRealBEPUTrianglesMode(bool enable)
{
SetDebugTrianglesMode(enable);
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Modo triángulos reales: {(enable ? "ACTIVADO" : "DESACTIVADO")}");
}
/// <summary>
/// ✅ NUEVO: Verifica si una curva tiene triángulos válidos en BEPU
/// </summary>
/// <param name="curve">Curva a verificar</param>
/// <returns>True si tiene triángulos válidos</returns>
public bool HasValidBEPUTriangles(simCurve curve)
{
if (curve == null) return false;
try
{
var triangles = curve.GetRealBEPUTriangles();
bool hasTriangles = triangles.Length > 0;
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Curva tiene {triangles.Length} triángulos válidos: {hasTriangles}");
return hasTriangles;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Error verificando triángulos: {ex.Message}");
return false;
}
}