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 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 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 simTransporte CurrentTransport { get; set; }
public ConstraintHandle CurrentMotor { get; set; } public ConstraintHandle CurrentMotor { get; set; }
public bool HasActiveMotor => CurrentMotor.Value != 0; public bool HasActiveMotor => CurrentMotor.Value != 0;
@ -1066,11 +1068,13 @@ namespace CtrEditor.Simulacion
return isOnTransports > 0; return isOnTransports > 0;
} }
// ✅ NUEVOS MÉTODOS - gestión de estado // ✅ SISTEMA SIMPLIFICADO: Métodos unificados de gestión de motores
public void AssignToTransport(simTransporte transport, ConstraintHandle motor) public void AssignLinearMotor(simTransporte transport, ConstraintHandle motor)
{ {
CurrentTransport = transport; RemoveCurrentMotor(); // Eliminar motor anterior automáticamente
CurrentMotor = motor; CurrentMotor = motor;
CurrentMotorType = MotorType.Linear;
CurrentTransport = transport;
if (transport.isBrake) if (transport.isBrake)
{ {
isOnBrakeTransport = true; 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; CurrentMotor = default;
CurrentMotorType = MotorType.None;
CurrentTransport = null;
isOnBrakeTransport = false; isOnBrakeTransport = false;
CurrentBrakeTransport = null; 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) public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
private List<Action> _deferredActions; 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 // ✅ SIMPLIFICADO: Propiedades esenciales únicamente
public float SpeedMetersPerSecond { get; private set; }
public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>(); 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; public event Action<simCurve> OnSpeedChanged;
// ✅ NUEVA REFERENCIA - para limpiar motors // ✅ REFERENCIA para limpiar motors
private SimulationManagerBEPU _simulationManager; private SimulationManagerBEPU _simulationManager;
// ✅ NUEVO: Direcciones tangenciales precalculadas para cada triángulo // ✅ NUEVO: Almacenar triángulos creados para acceso directo
internal Dictionary<BodyHandle, Vector3> _triangleDirections = new Dictionary<BodyHandle, Vector3>(); private Triangle[] _storedTriangles;
public float InnerRadius => _innerRadius; public float InnerRadius => _innerRadius;
public float OuterRadius => _outerRadius; public float OuterRadius => _outerRadius;
public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF 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 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) 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; _simulation = simulation;
_deferredActions = deferredActions; _deferredActions = deferredActions;
_simulationManager = simulationManager; // ✅ NUEVA REFERENCIA _simulationManager = simulationManager;
_innerRadius = innerRadius; _innerRadius = innerRadius;
_outerRadius = outerRadius; _outerRadius = outerRadius;
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes // ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle); _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle); _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); Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
} }
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento // ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
public void SetSpeed(float speed) public void SetSpeed(float speed)
{ {
Speed = -speed; Speed = speed; // Velocidad angular directa (sin inversión)
SpeedMetersPerSecond = Math.Abs(Speed) / simBase.SPEED_CONVERSION_FACTOR;
OnSpeedChanged?.Invoke(this); OnSpeedChanged?.Invoke(this);
} }
@ -1210,34 +1227,159 @@ namespace CtrEditor.Simulacion
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, curveSize, curveSize, 0f); 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 try
RemoveConnectedMotors();
// ✅ NUEVO: Limpiar cachés de direcciones
_triangleDirections.Clear();
// Remover todos los triángulos de la curva
if (_triangleBodyHandles != null && _simulation != null)
{ {
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 return info.ToString();
if (_originalTriangles != null) }
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(); base.RemoverBody();
} }
@ -1251,17 +1393,8 @@ namespace CtrEditor.Simulacion
// ✅ USAR REFERENCIA DIRECTA al SimulationManager // ✅ USAR REFERENCIA DIRECTA al SimulationManager
if (_simulationManager != null) if (_simulationManager != null)
{ {
// Limpiar motors del cuerpo principal // ✅ SIMPLIFICADO: Solo limpiar motors del cuerpo principal
_simulationManager.RemoveMotorsConnectedToBody(BodyHandle); _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 // Limpiar la lista local de botellas
@ -1300,237 +1433,84 @@ namespace CtrEditor.Simulacion
{ {
RemoverBody(); RemoverBody();
// Crear triángulos para la curva - los ángulos ya definen la forma correcta // ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
var triangles = CreateTriangulatedCurve(_innerRadius, _outerRadius, -_startAngle, -_endAngle); var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
// ✅ NUEVO: Almacenar triángulos originales para visualización debug // ✅ ALMACENAR triángulos para acceso directo
_originalTriangles = new List<List<Vector3>>(triangles); _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 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++)
// Crear un cuerpo principal invisible para referencia de posición {
CreateMainBody(position); triangleBuffer[i] = triangles[i];
} }
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
private void CreateMainBody(Vector3 position) var shapeIndex = _simulation.Shapes.Add(mesh);
{
// 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);
var bodyDescription = BodyDescription.CreateKinematic( var bodyDescription = BodyDescription.CreateKinematic(
new RigidPose(position), // Sin rotación - la forma ya está definida por los ángulos new RigidPose(position),
new CollidableDescription(shapeIndex, 0f), // Sin speculative margin new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa
new BodyActivityDescription(0.01f) new BodyActivityDescription(0.01f)
); );
BodyHandle = _simulation.Bodies.Add(bodyDescription); BodyHandle = _simulation.Bodies.Add(bodyDescription);
_bodyCreated = true; _bodyCreated = true;
}
private void CreateTriangleBody(List<Vector3> triangle, Vector3 basePosition) System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados");
{
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}");
} }
} }
private const float SegmentationFactor = 32f / 3f; // ✅ MÉTODOS ELIMINADOS: CreateMainBody y CreateTriangleBody ya no son necesarios
private const int MinSegments = 8; // La curva ahora se crea como un solo Mesh en el método Create simplificado
private const int MaxSegments = 64;
/// <summary> /// <summary>
/// Crea triángulos para representar una curva/arco en la simulación BEPU. /// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
/// IMPORTANTE: Los parámetros startAngle y endAngle ya están convertidos a radianes BEPU internamente, /// Solo superficie superior, eliminando complejidad innecesaria
/// pero además se invierte la dirección del arco para mantener coherencia visual con WPF. /// Los triángulos se crean en coordenadas locales (Z=0) y la posición del cuerpo los ubica correctamente
/// </summary> /// </summary>
/// <param name="innerRadius">Radio interno del arco</param> /// <param name="innerRadius">Radio interno del arco</param>
/// <param name="outerRadius">Radio externo 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="startAngle">Ángulo inicial en radianes BEPU</param>
/// <param name="endAngle">Ángulo final en radianes BEPU (ya convertido desde grados WPF)</param> /// <param name="endAngle">Ángulo final en radianes BEPU</param>
/// <returns>Lista de triángulos que forman el arco</returns> /// <returns>Array de triángulos nativos de BEPU en coordenadas locales</returns>
private List<List<Vector3>> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle) 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); float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
int segments = (int)(arcLength * SegmentationFactor); int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos
segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); float angleStep = (endAngle - startAngle) / segments;
// ✅ CRÍTICO: Inversión de dirección del arco para conversión WPF ↔ BEPU // ✅ SIMPLIFICADO: Sin inversión compleja de ángulos
//
// 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
for (int i = 0; i < segments; i++) for (int i = 0; i < segments; i++)
{ {
// Primer triángulo del sector float angle1 = startAngle + i * angleStep;
var upperTriangle = new List<Vector3> float angle2 = startAngle + (i + 1) * angleStep;
{
innerPoints[i], // ✅ COORDENADAS LOCALES: Triángulos en Z=0, el cuerpo los posiciona correctamente
outerPoints[i], var inner1 = new Vector3(innerRadius * (float)Math.Cos(angle1), innerRadius * (float)Math.Sin(angle1), 0);
outerPoints[i + 1] 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);
triangles.Add(upperTriangle); var outer2 = new Vector3(outerRadius * (float)Math.Cos(angle2), outerRadius * (float)Math.Sin(angle2), 0);
// Segundo triángulo del sector // ✅ USAR Triangle NATIVO DE BEPU (solo superficie superior)
var lowerTriangle = new List<Vector3> triangles.Add(new Triangle(inner1, outer1, outer2));
{ triangles.Add(new Triangle(inner1, outer2, inner2));
innerPoints[i],
outerPoints[i + 1],
innerPoints[i + 1]
};
triangles.Add(lowerTriangle);
} }
return triangles; System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales");
return triangles.ToArray();
} }
/// <summary> // ✅ MÉTODOS ELIMINADOS: Los cálculos tangenciales complejos ya no son necesarios
/// Calcula la dirección tangencial para aplicar fuerzas de curva. // AngularAxisMotor maneja automáticamente la rotación en curvas
/// 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);
}
} }
public class simDescarte : simBase public class simDescarte : simBase
@ -1712,11 +1692,11 @@ namespace CtrEditor.Simulacion
return false; // NO generar contacto físico para descartes 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 transportA = GetTransportFromCollidable(pair.A);
var transportB = GetTransportFromCollidable(pair.B); var transportB = GetTransportFromCollidable(pair.B);
(simCurve curveA, Vector3 directionA) = GetCurveAndDirectionFromCollidable(pair.A); var curveA = GetCurveFromCollidable(pair.A);
(simCurve curveB, Vector3 directionB) = GetCurveAndDirectionFromCollidable(pair.B); var curveB = GetCurveFromCollidable(pair.B);
// ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS // ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS
BodyHandle? bottleHandle = null; BodyHandle? bottleHandle = null;
@ -1741,23 +1721,21 @@ namespace CtrEditor.Simulacion
pairMaterial.MaximumRecoveryVelocity = 1f; pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6); 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)) else if (botella != null && (curveA != null || curveB != null))
{ {
var curve = curveA ?? curveB; var curve = curveA ?? curveB;
var direction = curveA != null ? directionA : directionB;
var triangleHandle = curveA != null ? pair.A.BodyHandle : pair.B.BodyHandle;
bottleHandle = botella.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); var pairKey = (bottleHandle.Value, elementHandle.Value);
if (!_simulationManager._motorsCreated.Contains(pairKey)) if (!_simulationManager._motorsCreated.Contains(pairKey))
{ {
_simulationManager.TryCreateCurveMotor(botella, curve, direction, triangleHandle); _simulationManager.TryCreateCurveAngularMotor(botella, curve);
_simulationManager._motorsCreated.Add(pairKey); _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) // Fricción alta para curvas (SIEMPRE aplicar para colisión física)
@ -1842,37 +1820,12 @@ namespace CtrEditor.Simulacion
if (collidable.Mobility == CollidableMobility.Kinematic) if (collidable.Mobility == CollidableMobility.Kinematic)
{ {
var bodyHandle = collidable.BodyHandle; var bodyHandle = collidable.BodyHandle;
var simBase = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle);
// ✅ NUEVO: Buscar específicamente en los triángulos de las curvas return simBase as simCurve;
return _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle);
} }
} }
return null; 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> /// <summary>
/// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold /// ✅ 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) 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 try
{ {
@ -2025,50 +1987,34 @@ namespace CtrEditor.Simulacion
!_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle)) !_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
return; return;
var direction = curve.GetCachedTangentialDirection(bottle.GetPosition()); // Verificar que la curva tenga velocidad
if (Math.Abs(curve.Speed) < 0.001f)
// Validar que la dirección no sea cero
if (direction.Length() < 0.001f)
return; return;
// ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA // ✅ SIMPLIFICADO: AngularAxisMotor para rotación en el plano XY
Vector3 localAxisDirection; var angularMotor = new AngularAxisMotor()
if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
{ {
var curveBody = _simulationManager.simulation.Bodies[curve.BodyHandle]; LocalAxisA = Vector3.UnitZ, // Eje Z para rotación en plano XY
var inverseCurveOrientation = Quaternion.Conjugate(curveBody.Pose.Orientation); TargetVelocity = curve.Speed, // Velocidad angular directa en rad/s
localAxisDirection = Vector3.Transform(direction, inverseCurveOrientation); Settings = new MotorSettings(Math.Max(bottle.Mass * 10f, 5f), 3f)
}
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)
}; };
var motorHandle = _simulationManager.simulation.Solver.Add( var motorHandle = _simulationManager.simulation.Solver.Add(
curve.BodyHandle, // ✅ USAR CUERPO DE LA CURVA curve.BodyHandle,
bottle.BodyHandle, bottle.BodyHandle,
motor angularMotor
); );
_activeMotors[bottle.BodyHandle] = motorHandle; _activeMotors[bottle.BodyHandle] = motorHandle;
_motorToBottle[motorHandle] = bottle; _motorToBottle[motorHandle] = bottle;
bottle.CurrentMotor = motorHandle; bottle.AssignAngularMotor(curve, motorHandle);
curve.BottlesOnCurve.Add(bottle); curve.BottlesOnCurve.Add(bottle);
System.Diagnostics.Debug.WriteLine($"[MotorManager] Angular motor created: {bottle.BodyHandle} → {curve.BodyHandle}");
} }
catch (Exception ex) 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, LocalOffsetA = Vector3.Zero,
LocalOffsetB = Vector3.Zero, LocalOffsetB = Vector3.Zero,
LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales 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) Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f)
}; };
@ -2242,7 +2188,7 @@ namespace CtrEditor.Simulacion
motor motor
); );
// Logging con coordenadas mundiales y locales para depuración // 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; _activeMotors[bottle.BodyHandle] = motorHandle;
_motorToBottle[motorHandle] = bottle; _motorToBottle[motorHandle] = bottle;
@ -2371,16 +2317,10 @@ namespace CtrEditor.Simulacion
_activeMotors.Clear(); _activeMotors.Clear();
_motorToBottle.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) if (_simulationManager?.Cuerpos != null)
{ {
var curves = _simulationManager.Cuerpos.OfType<simCurve>(); // Limpiar pares de motores creados
foreach (var curve in curves)
{
curve._triangleDirections?.Clear();
}
// También limpiar pares de motores creados
_simulationManager._motorsCreated?.Clear(); _simulationManager._motorsCreated?.Clear();
} }
} }
@ -2442,22 +2382,11 @@ namespace CtrEditor.Simulacion
} }
/// <summary> /// <summary>
/// ✅ NUEVO: Obtiene una simCurve si el BodyHandle pertenece a uno de sus triángulos /// ✅ SIMPLIFICADO: Obtiene una simCurve desde su BodyHandle principal
/// </summary> /// </summary>
public simCurve GetCurveFromTriangleBodyHandle(BodyHandle bodyHandle) public simCurve GetCurveFromTriangleBodyHandle(BodyHandle bodyHandle)
{ {
// Buscar en todas las curvas si alguna contiene este BodyHandle en sus triángulos // ✅ SIMPLIFICADO: Solo buscar en cuerpos principales (ya no hay triángulos separados)
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
var simBase = GetSimBaseFromBodyHandle(bodyHandle); var simBase = GetSimBaseFromBodyHandle(bodyHandle);
return simBase as simCurve; return simBase as simCurve;
} }
@ -2479,14 +2408,7 @@ namespace CtrEditor.Simulacion
break; break;
case simCurve curve: case simCurve curve:
_curveHandles.Add(curve.BodyHandle); _curveHandles.Add(curve.BodyHandle);
// También añadir todos sus triángulos // ✅ SIMPLIFICADO: Ya no hay triángulos separados, solo el cuerpo principal
if (curve._triangleBodyHandles != null)
{
foreach (var triangleHandle in curve._triangleBodyHandles)
{
_curveHandles.Add(triangleHandle);
}
}
curve.OnSpeedChanged += _motorManager.UpdateCurveSpeed; curve.OnSpeedChanged += _motorManager.UpdateCurveSpeed;
break; break;
case simBarrera barrier: case simBarrera barrier:
@ -2515,13 +2437,7 @@ namespace CtrEditor.Simulacion
break; break;
case simCurve curve: case simCurve curve:
_curveHandles.Remove(curve.BodyHandle); _curveHandles.Remove(curve.BodyHandle);
if (curve._triangleBodyHandles != null) // ✅ SIMPLIFICADO: Ya no hay triángulos separados
{
foreach (var triangleHandle in curve._triangleBodyHandles)
{
_curveHandles.Remove(triangleHandle);
}
}
curve.OnSpeedChanged -= _motorManager.UpdateCurveSpeed; curve.OnSpeedChanged -= _motorManager.UpdateCurveSpeed;
break; break;
case simBarrera barrier: case simBarrera barrier:
@ -2689,7 +2605,7 @@ namespace CtrEditor.Simulacion
foreach (var curve in Cuerpos.OfType<simCurve>()) foreach (var curve in Cuerpos.OfType<simCurve>())
{ {
// ✅ CORREGIDO: Usar método público para actualizar velocidad // ✅ 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(); 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> /// <summary>

View File

@ -386,153 +386,130 @@ namespace CtrEditor.Simulacion
} }
/// <summary> /// <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> /// </summary>
private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve) private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve)
{ {
try try
{ {
// Obtener los triángulos originales directamente de BEPU // ✅ EXTRAER TRIÁNGULOS REALES DE BEPU
var originalTriangles = curve.GetOriginalTriangles(); var realTriangles = curve.GetRealBEPUTriangles();
if (originalTriangles.Count == 0) 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; return;
} }
// Altura para simular triángulos planos (misma que en BEPU) System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {realTriangles.Length} triángulos reales de BEPU");
const float curveHeight = 0.05f; // simCurve.zAltura_Curve
// Convertir cada triángulo de BEPU a triángulos en Helix // ✅ USAR TRIÁNGULOS EXACTOS DE BEPU
foreach (var triangle in originalTriangles) foreach (var triangle in realTriangles)
{ {
if (triangle.Count != 3) // Convertir triángulos de BEPU a puntos 3D de Helix
{ var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z);
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle with {triangle.Count} vertices"); var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z);
continue; var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z);
}
// Agregar triángulo exacto al mesh
// Convertir Vector3 de BEPU a Point3D de Helix meshBuilder.AddTriangle(pointA, pointB, pointC);
// 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
} }
System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos reales de BEPU");
} }
catch (Exception ex) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating mesh from BEPU triangles: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos reales: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Fallback a geometría recreada");
// Fallback: usar el método anterior si falla la lectura de BEPU
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to recreated geometry");
CreateCurveMeshFallback(meshBuilder, curve); CreateCurveMeshFallback(meshBuilder, curve);
} }
} }
/// <summary> /// <summary>
/// ✅ NUEVO: Función de debug que muestra triángulos individuales de BEPU /// ✅ NUEVO: Función de debug que muestra triángulos individuales reales de BEPU
/// Cada triángulo se renderiza de manera separada para poder hacer debug visual /// Cada triángulo se renderiza de manera separada con offset para poder hacer debug visual
/// </summary> /// </summary>
private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve) private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve)
{ {
try try
{ {
// Obtener los triángulos originales directamente de BEPU // ✅ EXTRAER TRIÁNGULOS REALES DE BEPU PARA DEBUG
var originalTriangles = curve.GetOriginalTriangles(); var realTriangles = curve.GetRealBEPUTriangles();
if (originalTriangles.Count == 0) 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; 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) // ✅ MOSTRAR CADA TRIÁNGULO SEPARADO CON OFFSET PARA DEBUG
const float debugHeight = 0.02f; // Más bajo que la superficie normal para diferenciarlo float triangleSeparation = 0.02f; // Separación entre triángulos para debug visual
const float triangleSeparation = 0.01f; // Separación pequeña entre triángulos para distinguirlos
for (int i = 0; i < realTriangles.Length; i++)
// Convertir cada triángulo de BEPU a un triángulo 3D individual
for (int i = 0; i < originalTriangles.Count; i++)
{ {
var triangle = originalTriangles[i]; var triangle = realTriangles[i];
if (triangle.Count != 3) // Calcular centroide del triángulo
{ var centroid = (triangle.A + triangle.B + triangle.C) / 3f;
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle {i} with {triangle.Count} vertices");
continue; // Calcular normal del triángulo
} var edge1 = triangle.B - triangle.A;
var edge2 = triangle.C - triangle.A;
// Calcular el centro del triángulo para aplicar separación var normal = Vector3.Normalize(Vector3.Cross(edge1, edge2));
var center = new System.Numerics.Vector3(
(triangle[0].X + triangle[1].X + triangle[2].X) / 3f, // Offset hacia arriba para separar triángulos visualmente
(triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3f, var offset = normal * triangleSeparation * (i + 1);
0
); // Aplicar offset a todos los vértices
var offsetA = triangle.A + offset;
// Aplicar una pequeña separación desde el centro para distinguir triángulos var offsetB = triangle.B + offset;
var offset = center * triangleSeparation; var offsetC = triangle.C + offset;
// Crear triángulo superior (visible desde arriba) // Convertir a puntos 3D de Helix
var p1Top = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, debugHeight); var pointA = new Point3D(offsetA.X, offsetA.Y, offsetA.Z);
var p2Top = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, debugHeight); var pointB = new Point3D(offsetB.X, offsetB.Y, offsetB.Z);
var p3Top = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, debugHeight); var pointC = new Point3D(offsetC.X, offsetC.Y, offsetC.Z);
// Crear triángulo inferior (visible desde abajo) // Agregar triángulo separado
var p1Bottom = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, 0); meshBuilder.AddTriangle(pointA, pointB, pointC);
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); // ✅ DEBUG: Agregar bordes para visualizar mejor cada triángulo
AddDebugTriangleEdges(meshBuilder, pointA, pointB, pointC, 0.005f);
// 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);
} }
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) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating debug mesh: {ex.Message}"); 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"); System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to basic debug geometry");
CreateBasicDebugGeometry(meshBuilder, curve); 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> /// <summary>
/// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual /// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual
/// </summary> /// </summary>
@ -1071,8 +1048,8 @@ namespace CtrEditor.Simulacion
} }
/// <summary> /// <summary>
/// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos de una curva /// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos reales de BEPU
/// Útil para debug sin cambiar la visualización /// Extrae y muestra los triángulos exactos almacenados en la simulación física
/// </summary> /// </summary>
/// <param name="curve">Curva para analizar</param> /// <param name="curve">Curva para analizar</param>
/// <returns>Información de debug como string</returns> /// <returns>Información de debug como string</returns>
@ -1083,40 +1060,95 @@ namespace CtrEditor.Simulacion
try try
{ {
var triangles = curve.GetOriginalTriangles();
var debugInfo = new System.Text.StringBuilder(); 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($"Parámetros de curva:");
debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}"); debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}");
debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}"); debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}");
debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle} rad ({curve.StartAngle * 180 / Math.PI:F1}°)"); debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle}° ({curve.StartAngle * Math.PI / 180:F3} rad)");
debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle} rad ({curve.EndAngle * 180 / Math.PI:F1}°)"); debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle}° ({curve.EndAngle * Math.PI / 180:F3} rad)");
debugInfo.AppendLine($" - Velocidad: {curve.Speed} rad/s");
debugInfo.AppendLine($""); 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]; var triangle = realTriangles[i];
debugInfo.AppendLine($" Triángulo {i + 1}:"); debugInfo.AppendLine($"Triángulo {i + 1}:");
debugInfo.AppendLine($" P1: ({triangle[0].X:F3}, {triangle[0].Y:F3}, {triangle[0].Z:F3})"); debugInfo.AppendLine($" A: ({triangle.A.X:F4}, {triangle.A.Y:F4}, {triangle.A.Z:F4})");
debugInfo.AppendLine($" P2: ({triangle[1].X:F3}, {triangle[1].Y:F3}, {triangle[1].Z:F3})"); debugInfo.AppendLine($" B: ({triangle.B.X:F4}, {triangle.B.Y:F4}, {triangle.B.Z:F4})");
debugInfo.AppendLine($" P3: ({triangle[2].X:F3}, {triangle[2].Y:F3}, {triangle[2].Z:F3})"); 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($"");
debugInfo.AppendLine($"Modo debug actual: {(DebugShowIndividualTriangles ? "ACTIVADO" : "DESACTIVADO")}"); debugInfo.AppendLine($"Modo debug visual: {(DebugShowIndividualTriangles ? "ACTIVADO (triángulos separados)" : "DESACTIVADO (superficie continua)")}");
return debugInfo.ToString(); return debugInfo.ToString();
} }
catch (Exception ex) 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;
} }
} }