diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs
index d2218d0..b6e80f0 100644
--- a/Simulacion/BEPU.cs
+++ b/Simulacion/BEPU.cs
@@ -25,31 +25,31 @@ namespace CtrEditor.Simulacion
public static class CoordinateConverter
{
///
- /// Convierte ángulo de WPF a BEPU (invierte signo)
+ /// Convierte ángulo de WPF a BEPU (invierte signo) - USO INTERNO
///
- public static float WpfAngleToBepuAngle(float wpfAngle)
+ internal static float WpfAngleToBepuAngle(float wpfAngle)
{
return -wpfAngle;
}
///
- /// Convierte ángulo de BEPU a WPF (invierte signo)
+ /// Convierte ángulo de BEPU a WPF (invierte signo) - USO INTERNO
///
- public static float BepuAngleToWpfAngle(float bepuAngle)
+ internal static float BepuAngleToWpfAngle(float bepuAngle)
{
return -bepuAngle;
}
///
- /// Convierte posición Y de WPF a BEPU (invierte signo)
+ /// Convierte posición Y de WPF a BEPU (invierte signo) - USO INTERNO
///
- public static float WpfYToBepuY(float wpfY)
+ internal static float WpfYToBepuY(float wpfY)
{
return -wpfY;
}
///
- /// Convierte posición Y de BEPU a WPF (invierte signo)
+ /// Convierte posición Y de BEPU a WPF (invierte signo) - RETORNA VALOR WPF
///
public static float BepuYToWpfY(float bepuY)
{
@@ -57,15 +57,15 @@ namespace CtrEditor.Simulacion
}
///
- /// Convierte Vector2 de WPF a BEPU (solo invierte Y)
+ /// Convierte Vector2 de WPF a BEPU (solo invierte Y) - USO INTERNO
///
- public static Vector2 WpfToBepuVector2(Vector2 wpfVector)
+ internal static Vector2 WpfToBepuVector2(Vector2 wpfVector)
{
return new Vector2(wpfVector.X, -wpfVector.Y);
}
///
- /// Convierte Vector2 de BEPU a WPF (solo invierte Y)
+ /// Convierte Vector2 de BEPU a WPF (solo invierte Y) - RETORNA VALOR WPF
///
public static Vector2 BepuToWpfVector2(Vector2 bepuVector)
{
@@ -73,7 +73,7 @@ namespace CtrEditor.Simulacion
}
///
- /// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida)
+ /// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida) - RETORNA VALOR WPF
///
public static Vector2 BepuVector3ToWpfVector2(Vector3 bepuVector)
{
@@ -81,10 +81,10 @@ namespace CtrEditor.Simulacion
}
///
- /// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF
+ /// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF - USO INTERNO
/// Maneja correctamente la rotación del objeto
///
- public static Vector3 CalculateBepuCenterFromWpfTopLeft(Vector2 wpfTopLeft, float width, float height, float wpfAngle, float zPosition)
+ internal static Vector3 CalculateBepuCenterFromWpfTopLeft(Vector2 wpfTopLeft, float width, float height, float wpfAngle, float zPosition)
{
// Calcular el offset del centro desde Top-Left (sin rotación)
var offsetX = width / 2f;
@@ -108,7 +108,7 @@ namespace CtrEditor.Simulacion
}
///
- /// Calcula la posición Top-Left WPF desde el centro BEPU
+ /// Calcula la posición Top-Left WPF desde el centro BEPU - RETORNA VALOR WPF
/// Maneja correctamente la rotación del objeto
///
public static Vector2 CalculateWpfTopLeftFromBepuCenter(Vector3 bepuCenter, float width, float height, float wpfAngle)
@@ -138,16 +138,16 @@ namespace CtrEditor.Simulacion
}
///
- /// Crea un Quaternion para BEPU desde un ángulo WPF
+ /// Crea un Quaternion para BEPU desde un ángulo WPF - USO INTERNO
///
- public static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle)
+ internal static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle)
{
var bepuAngle = WpfAngleToBepuAngle(wpfAngle);
return Quaternion.CreateFromAxisAngle(Vector3.UnitZ, simBase.GradosARadianes(bepuAngle));
}
///
- /// Extrae el ángulo WPF desde un Quaternion BEPU
+ /// Extrae el ángulo WPF desde un Quaternion BEPU - RETORNA VALOR WPF
///
public static float ExtractWpfAngleFromBepuQuaternion(Quaternion bepuQuaternion)
{
@@ -162,9 +162,9 @@ namespace CtrEditor.Simulacion
}
///
- /// Actualiza la posición de un body BEPU manteniendo su rotación actual
+ /// Actualiza la posición de un body BEPU manteniendo su rotación actual - USO INTERNO
///
- public static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition)
+ internal static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition)
{
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
{
@@ -174,9 +174,9 @@ namespace CtrEditor.Simulacion
}
///
- /// Actualiza la rotación de un body BEPU manteniendo su posición actual
+ /// Actualiza la rotación de un body BEPU manteniendo su posición actual - USO INTERNO
///
- public static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle)
+ internal static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle)
{
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
{
@@ -186,9 +186,9 @@ namespace CtrEditor.Simulacion
}
///
- /// Actualiza posición y rotación de un body BEPU simultáneamente
+ /// Actualiza posición y rotación de un body BEPU simultáneamente - USO INTERNO
///
- public static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle)
+ internal static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle)
{
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
{
@@ -199,9 +199,9 @@ namespace CtrEditor.Simulacion
}
///
- /// Obtiene la posición del centro en coordenadas BEPU
+ /// Obtiene la posición del centro en coordenadas BEPU - USO INTERNO
///
- public static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle)
+ internal static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle)
{
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
{
@@ -212,7 +212,7 @@ namespace CtrEditor.Simulacion
}
///
- /// Obtiene el ángulo WPF desde un body BEPU
+ /// Obtiene el ángulo WPF desde un body BEPU - RETORNA VALOR WPF
///
public static float GetWpfAngleFromBepuBody(Simulation simulation, BodyHandle bodyHandle)
{
@@ -223,6 +223,24 @@ namespace CtrEditor.Simulacion
}
return 0f;
}
+
+ ///
+ /// ✅ NUEVO: Convierte directamente de grados WPF a radianes BEPU - USO INTERNO
+ /// Maneja tanto la inversión de signo como la conversión a radianes en una sola operación
+ ///
+ internal static float WpfDegreesToBepuRadians(float wpfDegrees)
+ {
+ return simBase.GradosARadianes(WpfAngleToBepuAngle(wpfDegrees));
+ }
+
+ ///
+ /// ✅ NUEVO: Convierte directamente de radianes BEPU a grados WPF - RETORNA VALOR WPF
+ /// Maneja tanto la conversión a grados como la inversión de signo en una sola operación
+ ///
+ public static float BepuRadiansToWpfDegrees(float bepuRadians)
+ {
+ return BepuAngleToWpfAngle(simBase.RadianesAGrados(bepuRadians));
+ }
}
public class simBase
@@ -467,7 +485,7 @@ namespace CtrEditor.Simulacion
{
// ✅ CORREGIDO: Aplicar conversión de coordenadas WPF→BEPU en el vector
var wpfAngle = GetRotationZ(); // Ángulo WPF en grados
- var wpfAngleRadians = GradosARadianes(wpfAngle);
+ var wpfAngleRadians = simBase.GradosARadianes(wpfAngle);
// Calcular el vector en coordenadas WPF
var wpfX = (float)Math.Cos(wpfAngleRadians);
@@ -480,7 +498,7 @@ namespace CtrEditor.Simulacion
System.Diagnostics.Debug.WriteLine($"[UpdateCached-Fallback] WPF Angle: {wpfAngle}°, WPF Vector: ({wpfX:F3}, {wpfY:F3}), BEPU DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
}
- SpeedMetersPerSecond = Speed / SPEED_CONVERSION_FACTOR;
+ SpeedMetersPerSecond = Speed / simBase.SPEED_CONVERSION_FACTOR;
}
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
@@ -499,7 +517,7 @@ namespace CtrEditor.Simulacion
/// Velocidad en m/s (típicamente entre -5.0 y 5.0)
public void SetTransportSpeed(float speedMeterPerSecond)
{
- SetSpeed(speedMeterPerSecond * SPEED_CONVERSION_FACTOR);
+ SetSpeed(speedMeterPerSecond * simBase.SPEED_CONVERSION_FACTOR);
}
///
@@ -951,7 +969,7 @@ namespace CtrEditor.Simulacion
new RigidPose(position),
new BodyVelocity(),
inertia, // Inercia estándar de esfera
- new CollidableDescription(shapeIndex, 0.01f),
+ new CollidableDescription(shapeIndex, 0.001f),
activityDescription
);
@@ -1070,19 +1088,26 @@ namespace CtrEditor.Simulacion
}
+ ///
+ /// Representa una curva o arco en la simulación física.
+ /// ✅ IMPORTANTE: Los ángulos _startAngle y _endAngle se almacenan INTERNAMENTE en radianes BEPU
+ /// (ya convertidos desde grados WPF usando CoordinateConverter.WpfDegreesToBepuRadians)
+ /// Las propiedades públicas StartAngle/EndAngle devuelven grados WPF usando CoordinateConverter.BepuRadiansToWpfDegrees
+ /// ✅ NUEVO: Cada triángulo actúa como un mini-transporte con su propia dirección tangencial fija
+ ///
public class simCurve : simBase
{
private float _innerRadius;
private float _outerRadius;
- private float _startAngle;
- private float _endAngle;
+ private float _startAngle; // ✅ SIEMPRE en radianes BEPU
+ private float _endAngle; // ✅ SIEMPRE en radianes BEPU
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
private List _deferredActions;
internal List _triangleBodyHandles; // Lista de todos los triángulos que componen la curva
private List> _originalTriangles; // ✅ NUEVO: Triángulos originales para visualización debug
- // ✅ NUEVAS PROPIEDADES - optimización
+ // ✅ SIMPLIFICADO: Solo necesitamos velocidad en m/s, no sistema complejo de direcciones
public float SpeedMetersPerSecond { get; private set; }
public List BottlesOnCurve { get; private set; } = new List();
@@ -1092,10 +1117,13 @@ namespace CtrEditor.Simulacion
// ✅ NUEVA REFERENCIA - para limpiar motors
private SimulationManagerBEPU _simulationManager;
+ // ✅ NUEVO: Direcciones tangenciales precalculadas para cada triángulo
+ internal Dictionary _triangleDirections = new Dictionary();
+
public float InnerRadius => _innerRadius;
public float OuterRadius => _outerRadius;
- public float StartAngle => RadianesAGrados(_startAngle); // Convertir de radianes BEPU internos a grados WPF
- public float EndAngle => RadianesAGrados(_endAngle); // 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
///
/// ✅ NUEVO: Expone los triángulos originales para visualización debug
@@ -1109,9 +1137,9 @@ namespace CtrEditor.Simulacion
_simulationManager = simulationManager; // ✅ NUEVA REFERENCIA
_innerRadius = innerRadius;
_outerRadius = outerRadius;
- // ✅ CORREGIDO: Usar conversión WPF a BEPU y luego a radianes para consistencia
- _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle));
- _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle));
+ // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
+ _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
+ _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
_triangleBodyHandles = new List();
_originalTriangles = new List>(); // ✅ NUEVO: Inicializar lista de triángulos
@@ -1123,7 +1151,7 @@ namespace CtrEditor.Simulacion
public void SetSpeed(float speed)
{
Speed = -speed;
- SpeedMetersPerSecond = Math.Abs(Speed) / SPEED_CONVERSION_FACTOR;
+ SpeedMetersPerSecond = Math.Abs(Speed) / simBase.SPEED_CONVERSION_FACTOR;
OnSpeedChanged?.Invoke(this);
}
@@ -1131,9 +1159,9 @@ namespace CtrEditor.Simulacion
{
_innerRadius = innerRadius;
_outerRadius = outerRadius;
- // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente
- _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle));
- _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle));
+ // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
+ _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
+ _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
// Recrear la curva con nuevos parámetros manteniendo posición actual
var currentPosition = GetPosition();
@@ -1148,9 +1176,9 @@ namespace CtrEditor.Simulacion
// Actualizar parámetros de la curva
_innerRadius = innerRadius;
_outerRadius = outerRadius;
- // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente
- _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle));
- _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle));
+ // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
+ _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
+ _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
// ✅ USAR COORDINATECONVERTER para conversión centralizada
var curveSize = outerRadius * 2f;
@@ -1187,6 +1215,9 @@ namespace CtrEditor.Simulacion
// ✅ 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)
{
@@ -1250,9 +1281,9 @@ namespace CtrEditor.Simulacion
// Actualizar parámetros internos
_innerRadius = innerRadius;
_outerRadius = outerRadius;
- // ✅ CORREGIDO: Usar conversión WPF a BEPU consistente
- _startAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(startAngle));
- _endAngle = GradosARadianes(CoordinateConverter.WpfAngleToBepuAngle(endAngle));
+ // ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
+ _startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
+ _endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
// ✅ USAR COORDINATECONVERTER para conversión centralizada
// Para curvas, el "tamaño" es el diámetro del radio exterior
@@ -1311,31 +1342,59 @@ namespace CtrEditor.Simulacion
var center = (triangle[0] + triangle[1] + triangle[2]) / 3f;
var worldCenter = center + basePosition;
- // 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);
+ // ✅ 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
+ 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 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);
+ // 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.1f), // Speculative margin normal
- activityDescription
- );
+ 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);
+ 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)
{
@@ -1347,6 +1406,16 @@ namespace CtrEditor.Simulacion
private const int MinSegments = 8;
private const int MaxSegments = 64;
+ ///
+ /// 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.
+ ///
+ /// Radio interno del arco
+ /// Radio externo del arco
+ /// Ángulo inicial en radianes BEPU (ya convertido desde grados WPF)
+ /// Ángulo final en radianes BEPU (ya convertido desde grados WPF)
+ /// Lista de triángulos que forman el arco
private List> CreateTriangulatedCurve(float innerRadius, float outerRadius, float startAngle, float endAngle)
{
var triangles = new List>();
@@ -1356,9 +1425,17 @@ namespace CtrEditor.Simulacion
int segments = (int)(arcLength * SegmentationFactor);
segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments));
- // ✅ SENTIDO HORARIO: Intercambiar ángulos para que vaya en sentido horario
- float fromAngle = endAngle; // Empezar desde el ángulo final
- float toAngle = startAngle; // Terminar en el ángulo inicial
+ // ✅ 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
@@ -1404,7 +1481,9 @@ namespace CtrEditor.Simulacion
///
- /// Calcula la dirección tangencial para aplicar fuerzas de curva
+ /// 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.
///
public Vector3 GetTangentialDirection(Vector3 bottlePosition)
{
@@ -1444,6 +1523,14 @@ namespace CtrEditor.Simulacion
var direction = GetTangentialDirection(bottlePosition);
return Speed < 0 ? -direction : direction;
}
+
+ ///
+ /// ✅ NUEVO - Obtiene la dirección tangencial fija de un triángulo específico
+ ///
+ public Vector3 GetTriangleDirection(BodyHandle triangleHandle)
+ {
+ return _triangleDirections.GetValueOrDefault(triangleHandle, Vector3.Zero);
+ }
}
public class simDescarte : simBase
@@ -1539,7 +1626,48 @@ namespace CtrEditor.Simulacion
public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b, ref float speculativeMargin)
{
- return true;
+ // ✅ NUEVO FILTRADO: Evitar colisiones innecesarias entre triángulos de curvas
+ if (_simulationManager != null)
+ {
+ // Obtener información de los objetos involucrados
+ var curveA = _simulationManager.GetCurveFromTriangleBodyHandle(a.BodyHandle);
+ var curveB = _simulationManager.GetCurveFromTriangleBodyHandle(b.BodyHandle);
+ var transportA = GetTransportFromCollidable(a);
+ var transportB = GetTransportFromCollidable(b);
+ var barrierA = GetBarreraFromCollidable(a);
+ var barrierB = GetBarreraFromCollidable(b);
+ var discardA = GetDescarteFromCollidable(a);
+ var discardB = GetDescarteFromCollidable(b);
+ var botellaA = GetBotellaFromCollidable(a);
+ var botellaB = GetBotellaFromCollidable(b);
+
+ // ✅ FILTRO 1: No permitir colisiones entre triángulos de curvas
+ if (curveA != null && curveB != null)
+ {
+ return false; // Los triángulos de curva no deben colisionar entre sí
+ }
+
+ // ✅ FILTRO 2: No permitir colisiones entre transportes
+ if (transportA != null && transportB != null)
+ {
+ return false; // Los transportes no deben colisionar entre sí
+ }
+
+ // ✅ FILTRO 3: No permitir colisiones entre elementos estáticos (transportes, curvas, barreras, descartes)
+ var staticA = (transportA != null || curveA != null || barrierA != null || discardA != null);
+ var staticB = (transportB != null || curveB != null || barrierB != null || discardB != null);
+
+ if (staticA && staticB)
+ {
+ return false; // Los elementos estáticos no deben colisionar entre sí
+ }
+
+ // ✅ ELIMINADO: No bloquear colisiones físicas aquí
+ // Las colisiones físicas deben permitirse siempre para mantener geometría sólida
+ // Solo bloqueamos la creación de motores duplicados en ConfigureContactManifold
+ }
+
+ return true; // Permitir todas las demás colisiones (botella-estático, botella-botella)
}
public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB)
@@ -1587,31 +1715,56 @@ namespace CtrEditor.Simulacion
// ✅ PROCESAR CREACIÓN DE MOTORES para transportes y curvas
var transportA = GetTransportFromCollidable(pair.A);
var transportB = GetTransportFromCollidable(pair.B);
- var curveA = GetCurveFromCollidable(pair.A);
- var curveB = GetCurveFromCollidable(pair.B);
+ (simCurve curveA, Vector3 directionA) = GetCurveAndDirectionFromCollidable(pair.A);
+ (simCurve curveB, Vector3 directionB) = GetCurveAndDirectionFromCollidable(pair.B);
- // ✅ RESTAURADO - llamar métodos públicos del SimulationManager
- // Si hay contacto botella-transporte, crear motor LinearAxisMotor
+ // ✅ VERIFICAR SI YA SE CREÓ MOTOR PARA EVITAR DUPLICADOS
+ BodyHandle? bottleHandle = null;
+ BodyHandle? elementHandle = null;
+
if (botella != null && (transportA != null || transportB != null))
{
var transport = transportA ?? transportB;
- _simulationManager.TryCreateTransportMotor(botella, transport); // ✅ Llamar método público
+ bottleHandle = botella.BodyHandle;
+ elementHandle = transport.BodyHandle;
- // Fricción alta para transportes
+ var pairKey = (bottleHandle.Value, elementHandle.Value);
+ if (!_simulationManager._motorsCreated.Contains(pairKey))
+ {
+ _simulationManager.TryCreateTransportMotor(botella, transport);
+ _simulationManager._motorsCreated.Add(pairKey);
+ System.Diagnostics.Debug.WriteLine($"[Manifold] Motor transporte: {bottleHandle} → {elementHandle}");
+ }
+
+ // Fricción alta para transportes (SIEMPRE aplicar para colisión física)
pairMaterial.FrictionCoefficient = 0.9f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
}
- // Si hay contacto botella-curva, crear motor LinearAxisMotor
+ // ✅ SIMPLIFICADO: Si hay contacto botella-triángulo de curva, crear motor con dirección específica
else if (botella != null && (curveA != null || curveB != null))
{
var curve = curveA ?? curveB;
- _simulationManager.TryCreateCurveMotor(botella, curve); // ✅ Llamar método público
+ var direction = curveA != null ? directionA : directionB;
+ var triangleHandle = curveA != null ? pair.A.BodyHandle : pair.B.BodyHandle;
- // Fricción alta para curvas
+ bottleHandle = botella.BodyHandle;
+ elementHandle = triangleHandle;
+
+ // ✅ SIMPLIFICADO: Crear motor usando sistema natural de colisiones de BEPU
+ var pairKey = (bottleHandle.Value, elementHandle.Value);
+ if (!_simulationManager._motorsCreated.Contains(pairKey))
+ {
+ _simulationManager.TryCreateCurveMotor(botella, curve, direction, triangleHandle);
+ _simulationManager._motorsCreated.Add(pairKey);
+ System.Diagnostics.Debug.WriteLine($"[Manifold] Motor curva: {bottleHandle} → {triangleHandle}");
+ }
+
+ // Fricción alta para curvas (SIEMPRE aplicar para colisión física)
pairMaterial.FrictionCoefficient = 0.9f;
pairMaterial.MaximumRecoveryVelocity = 1f;
pairMaterial.SpringSettings = new SpringSettings(80, 6);
+
}
// Solo ajustes básicos de fricción para otras botellas
else if (botella != null)
@@ -1696,6 +1849,30 @@ namespace CtrEditor.Simulacion
}
return null;
}
+
+ ///
+ /// ✅ NUEVO: Obtiene curva y dirección específica del triángulo
+ ///
+ 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);
+ }
///
/// ✅ NUEVO MÉTODO: Aplicar fuerzas de frenado directamente en el manifold
@@ -1854,12 +2031,26 @@ namespace CtrEditor.Simulacion
if (direction.Length() < 0.001f)
return;
- // ✅ USAR CUERPO DE LA CURVA EN LUGAR DE REFERENCIA ESTÁTICA
+ // ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA
+ Vector3 localAxisDirection;
+
+ if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
+ {
+ 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 = direction,
+ LocalAxis = localAxisDirection, // ✅ Usar dirección en coordenadas locales
TargetVelocity = curve.SpeedMetersPerSecond,
Settings = new MotorSettings(Math.Max(bottle.Mass * 20f, 8f), 4f)
};
@@ -1894,13 +2085,34 @@ namespace CtrEditor.Simulacion
if (_simulationManager.simulation.Solver != null)
{
_simulationManager.simulation.Solver.Remove(motorHandle);
+ System.Diagnostics.Debug.WriteLine($"[RemoveMotor] Motor eliminado: {motorHandle}");
}
-
+
_activeMotors.Remove(bottle.BodyHandle);
_motorToBottle.Remove(motorHandle);
+ // ✅ NUEVO: Limpiar pares de motores creados para esta botella
+ var bottleHandle = bottle.BodyHandle;
+ var motorsToRemove = _simulationManager._motorsCreated
+ .Where(pair => pair.bottle == bottleHandle)
+ .ToList();
+
+ foreach (var pair in motorsToRemove)
+ {
+ _simulationManager._motorsCreated.Remove(pair);
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Motor pair cleaned: {pair.bottle} - {pair.element}");
+ }
+
// Limpiar referencias de forma segura
bottle.CurrentTransport?.BottlesOnTransport.Remove(bottle);
+
+ // ✅ SIMPLIFICADO: Solo limpiar listas de botellas en curvas
+ var curves = _simulationManager.Cuerpos.OfType();
+ foreach (var curve in curves)
+ {
+ curve.BottlesOnCurve.Remove(bottle);
+ }
+
bottle.RemoveFromTransport();
}
}
@@ -1938,19 +2150,113 @@ namespace CtrEditor.Simulacion
}
}
- public void UpdateCurveMotorDirection(simBotella bottle, simCurve curve)
+ ///
+ /// ✅ NUEVO: Actualiza la dirección de un motor de curva existente para un nuevo triángulo
+ ///
+ public void UpdateCurveMotorDirection(simBotella bottle, simCurve curve, Vector3 newDirection, BodyHandle newTriangleHandle)
{
- // TODO: Implementar actualización de dirección cuando se resuelva la API correcta
- // Por ahora, recreamos el motor como alternativa para curvas
- if (_activeMotors.ContainsKey(bottle.BodyHandle))
+ try
{
- RemoveMotor(bottle);
- CreateCurveMotor(bottle, curve);
+ if (bottle == null || curve == null || _simulationManager?.simulation == null)
+ return;
+
+ if (!_activeMotors.ContainsKey(bottle.BodyHandle))
+ return; // No hay motor activo
+
+ // Validar que la dirección sea válida
+ if (newDirection.Length() < 0.001f)
+ return;
+
+ // ✅ ESTRATEGIA: Recrear motor con nueva dirección (más eficiente que actualizar en BEPU)
+ var oldMotorHandle = _activeMotors[bottle.BodyHandle];
+
+ // Eliminar motor anterior sin limpiar pares silenciados (los conservamos)
+ if (_simulationManager.simulation.Solver != null)
+ {
+ _simulationManager.simulation.Solver.Remove(oldMotorHandle);
+ // Motor anterior eliminado y recreado con nueva dirección
+ }
+
+ _activeMotors.Remove(bottle.BodyHandle);
+ _motorToBottle.Remove(oldMotorHandle);
+
+ // Crear nuevo motor con nueva dirección (reutilizar CreateCurveMotorWithDirection)
+ CreateCurveMotorWithDirection(bottle, curve, newDirection);
+
+ System.Diagnostics.Debug.WriteLine($"[UpdateCurveMotorDirection] Motor actualizado con nueva dirección para triángulo: {newTriangleHandle}");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error updating curve motor direction: {ex.Message}");
}
}
///
- /// ✅ NUEVO: Elimina todos los motors conectados a un body específico (transportes y curvas)
+ /// ✅ NUEVO - Crea motor de curva con dirección precalculada
+ ///
+ public void CreateCurveMotorWithDirection(simBotella bottle, simCurve curve, Vector3 direction)
+ {
+ try
+ {
+ // ✅ VALIDACIONES CRÍTICAS
+ if (bottle == null || curve == null || _simulationManager?.simulation == null)
+ return;
+
+ // Validar que los BodyHandle existan
+ if (!_simulationManager.simulation.Bodies.BodyExists(bottle.BodyHandle) ||
+ !_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
+ return;
+
+ // Validar que la dirección no sea cero
+ if (direction.Length() < 0.001f)
+ return;
+
+ // ✅ CONVERTIR DIRECCIÓN MUNDIAL A COORDENADAS LOCALES DE LA CURVA
+ // El cuerpo principal de la curva no tiene rotación, pero aún necesitamos coordenadas locales
+ Vector3 localAxisDirection;
+
+ if (_simulationManager.simulation.Bodies.BodyExists(curve.BodyHandle))
+ {
+ 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)
+ };
+
+ var motorHandle = _simulationManager.simulation.Solver.Add(
+ curve.BodyHandle,
+ bottle.BodyHandle,
+ 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}");
+
+ _activeMotors[bottle.BodyHandle] = motorHandle;
+ _motorToBottle[motorHandle] = bottle;
+ bottle.CurrentMotor = motorHandle;
+ curve.BottlesOnCurve.Add(bottle);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Error creating curve motor with direction: {ex.Message}");
+ }
+ }
+
+ ///
+ /// ✅ OPTIMIZADO: Elimina todos los motors conectados a un body específico de forma eficiente
///
public void RemoveMotorsByBodyHandle(BodyHandle bodyHandle)
{
@@ -1959,7 +2265,26 @@ namespace CtrEditor.Simulacion
if (_simulationManager?.simulation?.Solver == null)
return;
- // Encontrar todos los motors que involucran este body
+ // ✅ OPTIMIZACIÓN: Solo procesar si hay motors activos
+ if (_activeMotors.Count == 0)
+ {
+ return; // No hay motors, salir inmediatamente
+ }
+
+ // ✅ OPTIMIZACIÓN: Identificar rápidamente el tipo de body
+ var targetObject = _simulationManager.GetSimBaseFromBodyHandle(bodyHandle);
+ if (targetObject == null)
+ {
+ // ✅ Podría ser un triángulo de curva, buscar curva que lo contenga
+ var curve = _simulationManager.GetCurveFromTriangleBodyHandle(bodyHandle);
+ if (curve == null)
+ {
+ return; // No es un objeto relevante
+ }
+ targetObject = curve;
+ }
+
+ // Encontrar motors que deben eliminarse
var motorsToRemove = new List<(BodyHandle bottleHandle, ConstraintHandle motorHandle)>();
foreach (var kvp in _activeMotors)
@@ -1967,38 +2292,21 @@ namespace CtrEditor.Simulacion
var bottleHandle = kvp.Key;
var motorHandle = kvp.Value;
- // Verificar si esta botella tiene un motor conectado al body que se va a eliminar
if (_motorToBottle.TryGetValue(motorHandle, out var bottle))
{
bool shouldRemove = false;
- // Verificar transporte
- if (bottle.CurrentTransport?.BodyHandle.Equals(bodyHandle) == true)
+ // ✅ OPTIMIZADO: Verificación directa por tipo de objeto
+ switch (targetObject)
{
- shouldRemove = true;
- }
-
- // Verificar curvas (buscar en el cuerpo principal y triángulos)
- if (!shouldRemove)
- {
- var curves = _simulationManager.Cuerpos.OfType();
- foreach (var curve in curves)
- {
- // Verificar cuerpo principal de la curva
- if (curve.BodyHandle.Equals(bodyHandle))
- {
- shouldRemove = true;
- break;
- }
+ case simTransporte transport:
+ shouldRemove = bottle.CurrentTransport?.BodyHandle.Equals(bodyHandle) == true;
+ break;
- // Verificar triángulos de la curva
- if (curve._triangleBodyHandles != null &&
- curve._triangleBodyHandles.Contains(bodyHandle))
- {
- shouldRemove = true;
- break;
- }
- }
+ case simCurve curve:
+ // Verificar si la botella está en esta curva específica
+ shouldRemove = curve.BottlesOnCurve.Contains(bottle);
+ break;
}
if (shouldRemove)
@@ -2008,7 +2316,7 @@ namespace CtrEditor.Simulacion
}
}
- // Eliminar todos los motors encontrados
+ // Eliminar motors encontrados
foreach (var (bottleHandle, motorHandle) in motorsToRemove)
{
try
@@ -2018,7 +2326,7 @@ namespace CtrEditor.Simulacion
if (_motorToBottle.TryGetValue(motorHandle, out var bottle))
{
- bottle.RemoveFromTransport(); // Esto limpia tanto transporte como curva
+ bottle.RemoveFromTransport();
_motorToBottle.Remove(motorHandle);
}
}
@@ -2028,7 +2336,11 @@ namespace CtrEditor.Simulacion
}
}
- System.Diagnostics.Debug.WriteLine($"[MotorManager] Removed {motorsToRemove.Count} motors connected to body {bodyHandle}");
+ // ✅ SOLO LOG SI SE ELIMINARON MOTORS
+ if (motorsToRemove.Count > 0)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MotorManager] Removed {motorsToRemove.Count} motors connected to body {bodyHandle}");
+ }
}
catch (Exception ex)
{
@@ -2058,6 +2370,19 @@ namespace CtrEditor.Simulacion
}
_activeMotors.Clear();
_motorToBottle.Clear();
+
+ // ✅ NUEVO: Limpiar cachés de direcciones de todas las curvas y pares silenciados
+ if (_simulationManager?.Cuerpos != null)
+ {
+ var curves = _simulationManager.Cuerpos.OfType();
+ foreach (var curve in curves)
+ {
+ curve._triangleDirections?.Clear();
+ }
+
+ // También limpiar pares de motores creados
+ _simulationManager._motorsCreated?.Clear();
+ }
}
catch (Exception ex)
{
@@ -2091,8 +2416,8 @@ namespace CtrEditor.Simulacion
// ✅ NUEVO - sistema de motores
private MotorManager _motorManager;
- // ✅ NUEVO - tabla de exclusiones para callbacks silenciados
- private HashSet<(BodyHandle bottle, BodyHandle transport)> _silencedPairs;
+ // ✅ SEPARADO - tabla para bloquear creación de motores (NO bloquea colisiones físicas)
+ internal HashSet<(BodyHandle bottle, BodyHandle element)> _motorsCreated;
// ✅ NUEVO - contador de frames para optimizaciones
private int _frameCount = 0;
@@ -2181,8 +2506,8 @@ namespace CtrEditor.Simulacion
_bottleHandles.Remove(bottle.BodyHandle);
// Eliminar motor si existe
_motorManager.RemoveMotor(bottle);
- // Eliminar de pares silenciados
- RemoveFromSilencedPairs(bottle.BodyHandle);
+ // Eliminar de pares de motores creados
+ RemoveFromMotorsCreated(bottle.BodyHandle);
break;
case simTransporte transport:
_transportHandles.Remove(transport.BodyHandle);
@@ -2208,9 +2533,9 @@ namespace CtrEditor.Simulacion
}
}
- private void RemoveFromSilencedPairs(BodyHandle bottleHandle)
+ private void RemoveFromMotorsCreated(BodyHandle bottleHandle)
{
- _silencedPairs.RemoveWhere(pair => pair.bottle == bottleHandle);
+ _motorsCreated.RemoveWhere(pair => pair.bottle == bottleHandle);
}
// ✅ NUEVOS MÉTODOS - obtener objetos por handle
@@ -2249,7 +2574,7 @@ namespace CtrEditor.Simulacion
_barrierHandles = new HashSet();
_discardHandles = new HashSet();
_bottleHandles = new HashSet();
- _silencedPairs = new HashSet<(BodyHandle, BodyHandle)>();
+ _motorsCreated = new HashSet<(BodyHandle, BodyHandle)>();
// ✅ CONSERVAR - resto del constructor igual
bufferPool = new BufferPool();
@@ -2295,7 +2620,7 @@ namespace CtrEditor.Simulacion
_barreraContacts.Clear();
_descarteContacts.Clear();
_botellasParaEliminar.Clear();
- _silencedPairs.Clear(); // ✅ NUEVO
+ _motorsCreated.Clear(); // ✅ NUEVO
}
// ✅ NUEVO - limpiar clasificaciones
@@ -2445,8 +2770,8 @@ namespace CtrEditor.Simulacion
// ✅ NUEVO - verificación periódica de salidas de transporte
CheckBottleExitsFromTransports();
- // ✅ NUEVO - actualización periódica de direcciones de curvas
- UpdateCurveMotorDirections();
+ // ✅ ELIMINADO - UpdateCurveMotorDirections() ya no es necesario
+ // Los triángulos de curva funcionan como mini-transportes usando colisiones naturales
// ✅ CONSERVAR - limitación de rotación y mantener botellas despiertas
foreach (var cuerpo in Cuerpos.OfType().ToList())
@@ -2526,35 +2851,20 @@ namespace CtrEditor.Simulacion
private void ProcessBottleExitsTransport(simBotella bottle)
{
- UnsileceCurrentPair(bottle);
+ RemoveCurrentMotorPair(bottle);
_motorManager.RemoveMotor(bottle);
}
- private void UnsileceCurrentPair(simBotella bottle)
+ private void RemoveCurrentMotorPair(simBotella bottle)
{
if (bottle.CurrentTransport != null)
{
- _silencedPairs.Remove((bottle.BodyHandle, bottle.CurrentTransport.BodyHandle));
+ _motorsCreated.Remove((bottle.BodyHandle, bottle.CurrentTransport.BodyHandle));
}
}
- private void UpdateCurveMotorDirections()
- {
- // ✅ Ejecutar cada 5 frames para eficiencia
- if (_frameCount % 5 != 0) return;
-
- var curvesWithBottles = Cuerpos.OfType()
- .Where(c => c.BottlesOnCurve.Count > 0)
- .ToList();
-
- foreach (var curve in curvesWithBottles)
- {
- foreach (var bottle in curve.BottlesOnCurve.ToList())
- {
- _motorManager.UpdateCurveMotorDirection(bottle, curve);
- }
- }
- }
+ // ✅ ELIMINADO: Ya no necesitamos UpdateCurveMotorDirections
+ // Cada triángulo de curva funciona como un mini-transporte usando el sistema de colisiones natural
public void Remove(simBase Objeto)
{
@@ -2661,6 +2971,8 @@ namespace CtrEditor.Simulacion
_motorManager?.RemoveMotorsByBodyHandle(bodyHandle);
}
+
+
public void Dispose()
{
Clear();
@@ -2683,20 +2995,17 @@ namespace CtrEditor.Simulacion
if (bottle == null || transport == null || _motorManager == null)
return;
- // Verificar si ya tiene un motor activo o está silenciado
+ // Verificar si ya tiene un motor activo
if (bottle.HasActiveMotor)
return;
- var pairKey = (bottle.BodyHandle, transport.BodyHandle);
- if (_silencedPairs.Contains(pairKey))
- return;
-
// Asegurar que el transporte tenga propiedades inicializadas
if (Math.Abs(transport.Speed) < 0.001f)
return; // No crear motor para transportes detenidos
_motorManager.CreateTransportMotor(bottle, transport);
- _silencedPairs.Add(pairKey);
+
+ System.Diagnostics.Debug.WriteLine($"[TryCreateTransportMotor] Motor creado: {bottle.BodyHandle} - {transport.BodyHandle}");
}
catch (Exception ex)
{
@@ -2704,33 +3013,7 @@ namespace CtrEditor.Simulacion
}
}
- ///
- /// ✅ IMPLEMENTADO: Intenta crear un motor de curva si no existe uno activo
- ///
- public void TryCreateCurveMotor(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;
-
- // Verificar que la curva tenga velocidad
- if (Math.Abs(curve.Speed) < 0.001f)
- return; // No crear motor para curvas detenidas
-
- // ✅ Para curvas no silenciamos porque la dirección cambia constantemente
- _motorManager.CreateCurveMotor(bottle, curve);
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveMotor: {ex.Message}");
- }
- }
+
///
/// Registra un contacto entre una barrera y una botella para detección de paso
@@ -3198,5 +3481,55 @@ namespace CtrEditor.Simulacion
return lineStart + lineDirection * projectionLength;
}
+
+ ///
+ /// ✅ MODIFICADO: Sistema dinámico de motores de curva - permite actualización entre triángulos
+ ///
+ public void TryCreateCurveMotor(simBotella bottle, simCurve curve, Vector3 triangleDirection, BodyHandle triangleHandle)
+ {
+ try
+ {
+ // Validaciones básicas
+ if (bottle == null || curve == null || _motorManager == null)
+ return;
+
+ // Verificar que la curva tenga velocidad
+ if (Math.Abs(curve.Speed) < 0.001f)
+ return; // No crear motor para curvas detenidas
+
+ // Verificar que la dirección del triángulo sea válida
+ if (triangleDirection.Length() < 0.001f)
+ return;
+
+ // ✅ NUEVO: Si ya tiene motor de curva, actualizarlo; si no, crear uno nuevo
+ if (bottle.HasActiveMotor)
+ {
+ // Verificar si el motor actual es de una curva
+ var currentCurves = Cuerpos.OfType().Where(c => c.BottlesOnCurve.Contains(bottle));
+ if (currentCurves.Any())
+ {
+ // Actualizar motor existente con nueva dirección del triángulo
+ _motorManager.UpdateCurveMotorDirection(bottle, curve, triangleDirection, triangleHandle);
+
+ System.Diagnostics.Debug.WriteLine($"[TryCreateCurveMotor] Motor actualizado: {bottle.BodyHandle} → Triangle {triangleHandle}");
+ return;
+ }
+ else
+ {
+ // Tiene motor de transporte, no interferir
+ return;
+ }
+ }
+
+ // ✅ Crear nuevo motor de curva
+ _motorManager.CreateCurveMotorWithDirection(bottle, curve, triangleDirection);
+
+ System.Diagnostics.Debug.WriteLine($"[TryCreateCurveMotor] Nuevo motor: {bottle.BodyHandle} → Triangle {triangleHandle}");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[SimulationManager] Error in TryCreateCurveMotor with direction: {ex.Message}");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Simulacion/BEPUVisualization3D.cs b/Simulacion/BEPUVisualization3D.cs
index e98f69b..35f8aef 100644
--- a/Simulacion/BEPUVisualization3D.cs
+++ b/Simulacion/BEPUVisualization3D.cs
@@ -45,6 +45,24 @@ namespace CtrEditor.Simulacion
///
/// Manager que sincroniza el mundo 3D de BEPU con la visualización HelixToolkit
/// Usa simBase como clave única para evitar problemas de reutilización de BodyHandle
+ ///
+ /// ✅ NUEVO: Funcionalidad de Debug para Triángulos
+ /// Para activar la visualización de triángulos individuales de curvas:
+ ///
+ /// // Activar modo debug
+ /// visualizationManager.SetDebugTrianglesMode(true);
+ ///
+ /// // Desactivar modo debug (volver a superficie continua)
+ /// visualizationManager.SetDebugTrianglesMode(false);
+ ///
+ /// // Verificar si está activo
+ /// bool isDebugActive = visualizationManager.IsDebugTrianglesModeEnabled();
+ ///
+ /// En modo debug:
+ /// - Cada triángulo de BEPU se muestra separado con un pequeño offset
+ /// - Los triángulos tienen bordes wireframe para mejor visualización
+ /// - Se usa un material naranja semi-transparente para distinguir del modo normal
+ /// - Se muestran mensajes de debug en la consola con información detallada
///
public class BEPUVisualization3DManager
{
@@ -53,6 +71,9 @@ namespace CtrEditor.Simulacion
private Dictionary simBaseToModelMap;
private Dictionary lastKnownDimensions;
+ // ✅ NUEVO: Flag de debug para mostrar triángulos individuales de curvas
+ public static bool DebugShowIndividualTriangles { get; set; } = true;
+
public HelixViewport3D Viewport3D
{
get => viewport3D;
@@ -221,20 +242,17 @@ namespace CtrEditor.Simulacion
// Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro
if (simObj is simBotella botella)
{
- System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CYLINDER visualization for simBotella (BEPU uses sphere)");
visual3D = CreateCylinderVisualization(botella.Radius, botella.Height, simObj);
}
// Caso especial: simCurve usa múltiples triángulos pero se visualiza como superficie curva
else if (simObj is simCurve curve)
{
- System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CURVE visualization for simCurve");
visual3D = CreateCurveVisualization(curve);
}
else
{
// Para otros objetos, usar la forma real de BEPU
var dimensions = GetShapeDimensions(shapeIndex, simObj);
- System.Diagnostics.Debug.WriteLine($"[3D Create] Shape type: {dimensions.ShapeType}, Radius: {dimensions.Radius}");
if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id)
{
@@ -269,8 +287,6 @@ namespace CtrEditor.Simulacion
// Agregar a la vista 3D y asociar con simBase
viewport3D.Children.Add(visual3D);
simBaseToModelMap[simObj] = visual3D;
-
- System.Diagnostics.Debug.WriteLine($"[3D Create] Successfully created 3D visualization for {simObj.GetType().Name}");
}
}
catch (Exception ex)
@@ -338,11 +354,25 @@ namespace CtrEditor.Simulacion
{
var meshBuilder = new MeshBuilder();
- // ✅ NUEVO: Usar directamente los triángulos originales de BEPU (debug real)
- CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
+ // ✅ NUEVO: Elegir entre visualización normal o debug según el flag
+ if (DebugShowIndividualTriangles)
+ {
+ CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve);
+ }
+ else
+ {
+ // ✅ Usar directamente los triángulos originales de BEPU (superficie continua)
+ CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
+ }
var geometry = meshBuilder.ToMesh();
- var model = new GeometryModel3D(geometry, GetMaterialForSimBase(curve));
+
+ // ✅ NUEVO: Usar material específico para debug si está activo
+ Material material = DebugShowIndividualTriangles ?
+ GetDebugMaterialForCurve(curve) :
+ GetMaterialForSimBase(curve);
+
+ var model = new GeometryModel3D(geometry, material);
var visual = new ModelVisual3D();
visual.Content = model;
@@ -365,8 +395,6 @@ namespace CtrEditor.Simulacion
// Obtener los triángulos originales directamente de BEPU
var originalTriangles = curve.GetOriginalTriangles();
- System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating curve mesh from {originalTriangles.Count} BEPU triangles");
-
if (originalTriangles.Count == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
@@ -410,7 +438,6 @@ namespace CtrEditor.Simulacion
meshBuilder.AddQuad(p3Bottom, p1Bottom, p1Top, p3Top); // Lado 3-1
}
- System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created mesh from BEPU triangles");
}
catch (Exception ex)
{
@@ -422,6 +449,136 @@ namespace CtrEditor.Simulacion
}
}
+ ///
+ /// ✅ 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
+ ///
+ private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve)
+ {
+ try
+ {
+ // Obtener los triángulos originales directamente de BEPU
+ var originalTriangles = curve.GetOriginalTriangles();
+
+ if (originalTriangles.Count == 0)
+ {
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
+ return;
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating debug visualization for {originalTriangles.Count} triangles");
+
+ // 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++)
+ {
+ 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);
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created debug mesh with {originalTriangles.Count} individual triangles");
+ }
+ 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);
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual
+ ///
+ private void AddDebugEdge(MeshBuilder meshBuilder, Point3D p1Top, Point3D p2Top, Point3D p1Bottom, Point3D p2Bottom, float thickness)
+ {
+ try
+ {
+ // Crear un cilindro muy delgado como borde
+ meshBuilder.AddCylinder(p1Top, p2Top, thickness, 4, false, false);
+ meshBuilder.AddCylinder(p1Bottom, p2Bottom, thickness, 4, false, false);
+ meshBuilder.AddCylinder(p1Top, p1Bottom, thickness, 4, false, false);
+ meshBuilder.AddCylinder(p2Top, p2Bottom, thickness, 4, false, false);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR adding debug edge: {ex.Message}");
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Geometría básica de debug cuando fallan otras opciones
+ ///
+ private void CreateBasicDebugGeometry(MeshBuilder meshBuilder, simCurve curve)
+ {
+ try
+ {
+ // Crear una geometría muy simple que indique que hay un problema
+ float innerRadius = curve.InnerRadius;
+ float outerRadius = curve.OuterRadius;
+ float centerRadius = (innerRadius + outerRadius) / 2f;
+
+ // Crear un triángulo básico como indicador de debug
+ var p1 = new Point3D(centerRadius, 0, 0);
+ var p2 = new Point3D(centerRadius * Math.Cos(Math.PI/3), centerRadius * Math.Sin(Math.PI/3), 0);
+ var p3 = new Point3D(centerRadius * Math.Cos(-Math.PI/3), centerRadius * Math.Sin(-Math.PI/3), 0);
+
+ meshBuilder.AddTriangle(p1, p2, p3);
+
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Created basic debug geometry as fallback");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating basic debug geometry: {ex.Message}");
+ }
+ }
+
///
/// Método fallback que recrea la geometría (mantener por compatibilidad)
///
@@ -519,23 +676,15 @@ namespace CtrEditor.Simulacion
{
dimensionsChanged = !currentDimensions.Equals(lastDimensions);
- // ✅ LOGGING: Debug para curvas
- if (simObj is simCurve && dimensionsChanged)
- {
- System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! Old: Inner={lastDimensions.InnerRadius}, Outer={lastDimensions.OuterRadius}, Start={lastDimensions.StartAngle}, End={lastDimensions.EndAngle}");
- System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! New: Inner={currentDimensions.InnerRadius}, Outer={currentDimensions.OuterRadius}, Start={currentDimensions.StartAngle}, End={currentDimensions.EndAngle}");
- }
}
else
{
dimensionsChanged = true; // Primera vez
- System.Diagnostics.Debug.WriteLine($"[3D Update] First time creating geometry for {simObj.GetType().Name}");
}
// Si las dimensiones cambiaron, recrear la geometría
if (dimensionsChanged)
{
- System.Diagnostics.Debug.WriteLine($"[3D Update] Recreating geometry for {simObj.GetType().Name}");
RecreateVisualizationGeometry(simObj, visual, currentDimensions);
lastKnownDimensions[simObj] = currentDimensions;
}
@@ -595,7 +744,6 @@ namespace CtrEditor.Simulacion
dimensions.StartAngle = curve.StartAngle;
dimensions.EndAngle = curve.EndAngle;
dimensions.ShapeType = -1; // Tipo especial para curvas
- System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] Curve - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}");
return dimensions;
}
@@ -668,17 +816,21 @@ namespace CtrEditor.Simulacion
// Caso especial: simCurve necesita recreación completa de geometría
else if (simObj is simCurve curve)
{
- System.Diagnostics.Debug.WriteLine($"[3D Recreate] Creating CURVE mesh from BEPU triangles - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}");
-
var meshBuilder = new MeshBuilder();
- // ✅ NUEVO: Usar directamente los triángulos de BEPU (debug real)
- CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
+ // ✅ CORREGIDO: Respetar el flag de debug también en recreación
+ if (DebugShowIndividualTriangles)
+ {
+ CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve);
+ newMaterial = GetDebugMaterialForCurve(curve);
+ }
+ else
+ {
+ CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
+ newMaterial = GetMaterialForSimBase(simObj);
+ }
newGeometry = meshBuilder.ToMesh();
- newMaterial = GetMaterialForSimBase(simObj);
-
- System.Diagnostics.Debug.WriteLine($"[3D Recreate] CURVE mesh created successfully with {newGeometry?.Positions?.Count ?? 0} vertices");
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id)
{
@@ -711,7 +863,6 @@ namespace CtrEditor.Simulacion
{
geometryModel.Geometry = newGeometry;
geometryModel.Material = newMaterial;
- System.Diagnostics.Debug.WriteLine($"[3D Recreate] Successfully recreated geometry for {simObj.GetType().Name}");
}
}
catch (Exception ex)
@@ -730,6 +881,25 @@ namespace CtrEditor.Simulacion
lastKnownDimensions.Clear();
}
+ ///
+ /// ✅ NUEVO: Material específico para debug de curvas
+ ///
+ /// Curva en modo debug
+ /// Material especial para visualización de debug
+ private Material GetDebugMaterialForCurve(simCurve curve)
+ {
+ // Material wireframe semi-transparente para debug
+ // Color naranja brillante para que sea muy visible
+ var debugBrush = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // Naranja
+ debugBrush.Opacity = 0.7; // 70% opacidad para ver superposiciones
+
+ return MaterialHelper.CreateMaterial(
+ debugBrush,
+ specularPower: 20, // Menos reflectante para mejor visibilidad
+ ambient: 250 // Más ambiente para que se vea bien en todas las condiciones
+ );
+ }
+
///
/// Función centralizada para obtener el material apropiado según el tipo de simBase
///
@@ -837,6 +1007,119 @@ namespace CtrEditor.Simulacion
}
}
+ ///
+ /// ✅ NUEVO: Activa o desactiva el modo debug para mostrar triángulos individuales de curvas
+ ///
+ /// True para activar debug, false para modo normal
+ /// True para forzar actualización inmediata de todas las curvas
+ public void SetDebugTrianglesMode(bool enableDebug, bool forceRefresh = true)
+ {
+ bool wasChanged = DebugShowIndividualTriangles != enableDebug;
+ DebugShowIndividualTriangles = enableDebug;
+
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Debug triangles mode: {(enableDebug ? "ENABLED" : "DISABLED")}");
+
+ if (wasChanged && forceRefresh)
+ {
+ RefreshCurveVisualizations();
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Fuerza la regeneración de todas las visualizaciones de curvas
+ /// Útil cuando se cambia el modo debug
+ ///
+ public void RefreshCurveVisualizations()
+ {
+ if (simulationManager?.Cuerpos == null)
+ return;
+
+ try
+ {
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshing curve visualizations...");
+
+ // Encontrar todas las curvas y forzar su regeneración
+ var curvesToRefresh = simulationManager.Cuerpos.OfType().ToList();
+
+ foreach (var curve in curvesToRefresh)
+ {
+ // Remover las dimensiones conocidas para forzar recreación
+ lastKnownDimensions.Remove(curve);
+
+ // Forzar actualización de la visualización
+ if (simBaseToModelMap.ContainsKey(curve))
+ {
+ UpdateVisualizationFromSimBase(curve);
+ }
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshed {curvesToRefresh.Count} curve visualizations");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR refreshing curve visualizations: {ex.Message}");
+ }
+ }
+
+ ///
+ /// ✅ NUEVO: Obtiene el estado actual del modo debug
+ ///
+ /// True si el modo debug está activo
+ public bool IsDebugTrianglesModeEnabled()
+ {
+ return DebugShowIndividualTriangles;
+ }
+
+ ///
+ /// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos de una curva
+ /// Útil para debug sin cambiar la visualización
+ ///
+ /// Curva para analizar
+ /// Información de debug como string
+ public string GetCurveDebugInfo(simCurve curve)
+ {
+ if (curve == null)
+ return "ERROR: Curva es null";
+
+ try
+ {
+ var triangles = curve.GetOriginalTriangles();
+ var debugInfo = new System.Text.StringBuilder();
+
+ debugInfo.AppendLine($"=== DEBUG INFO PARA CURVA ===");
+ 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($"");
+ 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
+ {
+ 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})");
+ }
+
+ if (triangles.Count > 10)
+ {
+ debugInfo.AppendLine($" ... y {triangles.Count - 10} triángulos más");
+ }
+
+ debugInfo.AppendLine($"");
+ debugInfo.AppendLine($"Modo debug actual: {(DebugShowIndividualTriangles ? "ACTIVADO" : "DESACTIVADO")}");
+
+ return debugInfo.ToString();
+ }
+ catch (Exception ex)
+ {
+ return $"ERROR obteniendo info de debug: {ex.Message}";
+ }
+ }
+
///
/// Carga el estado guardado de la cámara desde EstadoPersistente
///
@@ -854,7 +1137,6 @@ namespace CtrEditor.Simulacion
FieldOfView = cameraSettings.FieldOfView
};
- System.Diagnostics.Debug.WriteLine($"[3D Camera] Loaded camera state from persistent storage");
}
catch (Exception ex)
{
@@ -893,7 +1175,6 @@ namespace CtrEditor.Simulacion
cameraSettings.FieldOfView = camera.FieldOfView;
EstadoPersistente.Instance.GuardarEstado();
- System.Diagnostics.Debug.WriteLine($"[3D Camera] Saved camera state to persistent storage");
}
}
catch (Exception ex)
@@ -916,7 +1197,6 @@ namespace CtrEditor.Simulacion
viewport3D.MouseUp += (sender, e) => SaveCameraState();
viewport3D.MouseWheel += (sender, e) => SaveCameraState();
- System.Diagnostics.Debug.WriteLine($"[3D Camera] Subscribed to camera change events");
}
catch (Exception ex)
{