Se realizaron mejoras en la gestión de barreras en el sistema de simulación. Se eliminó la dependencia de cuerpos físicos para las barreras, implementando un sistema de detección basado en RayCast. Se actualizaron los métodos de creación y actualización de barreras, así como su visualización en 3D, optimizando la representación del haz de luz. Además, se simplificó la lógica de detección y se eliminaron métodos obsoletos relacionados con la geometría de barreras.
This commit is contained in:
parent
095228144a
commit
a6cbd8c4ab
|
@ -94,12 +94,6 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Ancho del Haz")]
|
||||
float ancho_Haz_De_Luz;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Distancia al cuello de la botella")]
|
||||
[property: Category("Información")]
|
||||
[property: Name("Distancia al Cuello")]
|
||||
float distancia_cuello;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Tipo de detección: cuello de botella o botella completa")]
|
||||
[property: Category("Configuración")]
|
||||
|
@ -110,7 +104,7 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
if (Simulation_Photocell == null) return;
|
||||
|
||||
Simulation_Photocell.DetectNeck = value;
|
||||
simulationManager.SetBarreraDetectNeck(Simulation_Photocell, value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -221,11 +215,14 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
public override void UpdateControl(int elapsedMilliseconds)
|
||||
{
|
||||
Distancia_cuello = Simulation_Photocell.Distancia;
|
||||
if (Simulation_Photocell == null) return;
|
||||
|
||||
var barreraData = simulationManager.GetBarreraData(Simulation_Photocell);
|
||||
|
||||
if (DetectarCuello)
|
||||
LuzCortada = Simulation_Photocell.LuzCortadaNeck;
|
||||
LuzCortada = barreraData.LuzCortadaNeck;
|
||||
else
|
||||
LuzCortada = Simulation_Photocell.LuzCortada; // Ahora es bool directamente
|
||||
LuzCortada = barreraData.LuzCortada;
|
||||
}
|
||||
public override void UpdatePLCPrimerCiclo()
|
||||
{
|
||||
|
@ -250,7 +247,11 @@ namespace CtrEditor.ObjetosSim
|
|||
{
|
||||
// El UserControl se esta eliminando
|
||||
// eliminar el objeto de simulacion
|
||||
simulationManager.Remove(Simulation_Photocell);
|
||||
if (Simulation_Photocell != null)
|
||||
{
|
||||
simulationManager.RemoveBarrera(Simulation_Photocell);
|
||||
Simulation_Photocell = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1575,46 +1575,15 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
}
|
||||
|
||||
public void UpdateRectangle(simBarrera simRect, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||
public void UpdateRectangle(simBarrera barrera, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||
{
|
||||
if (simRect != null)
|
||||
if (barrera != null)
|
||||
{
|
||||
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||
|
||||
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
|
||||
bool dimensionsChanged = HasBarrierDimensionsChanged(simRect, Ancho, Alto);
|
||||
|
||||
if (dimensionsChanged)
|
||||
{
|
||||
// Las dimensiones cambiaron, recrear el objeto físico
|
||||
simRect.Create(Ancho, Alto, topLeft2D, Angulo);
|
||||
simRect.SetDimensions(Ancho, Alto); // Actualizar dimensiones internas
|
||||
}
|
||||
else
|
||||
{
|
||||
// ✅ CORREGIDO: Usar UpdateFromWpfParameters para manejar correctamente Top-Left + Ángulo
|
||||
simRect.UpdateFromWpfParameters(topLeft2D, Angulo);
|
||||
}
|
||||
simulationManager.UpdateBarrera(barrera, Ancho, Alto, topLeft2D, Angulo);
|
||||
}
|
||||
}
|
||||
|
||||
// Nota: simCurve no está implementado en BEPU, estos métodos están comentados por compatibilidad
|
||||
/*
|
||||
public void UpdateCurve(simCurve curva, float RadioInterno, float RadioExterno, float startAngle, float endAngle)
|
||||
{
|
||||
var center2D = GetCurveCenterInMeter(RadioExterno);
|
||||
var center3D = new Vector3(center2D.X, center2D.Y, 0);
|
||||
curva.Create(RadioInterno, RadioExterno, startAngle, endAngle, center3D);
|
||||
}
|
||||
|
||||
public simCurve AddCurve(float RadioInterno, float RadioExterno, float startAngle, float endAngle)
|
||||
{
|
||||
var center2D = GetCurveCenterInMeter(RadioExterno);
|
||||
var center3D = new Vector3(center2D.X, center2D.Y, 0);
|
||||
return simulationManager.AddCurve(RadioInterno, RadioExterno, startAngle, endAngle, center3D);
|
||||
}
|
||||
*/
|
||||
|
||||
public simTransporte AddRectangle(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo)
|
||||
{
|
||||
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||
|
@ -1624,7 +1593,8 @@ namespace CtrEditor.ObjetosSim
|
|||
public simBarrera AddBarrera(SimulationManagerBEPU simulationManager, Rectangle wpfRect, float Alto, float Ancho, float Angulo, bool detectarCuello)
|
||||
{
|
||||
var topLeft2D = GetRectangleTopLeft(wpfRect);
|
||||
return simulationManager.AddBarrera(Ancho, Alto, topLeft2D, Angulo, detectarCuello);
|
||||
// Convertir Vector2 a Vector3 para BEPU (Z = 0 para objetos 2D)
|
||||
return simulationManager.CreateBarrera(Ancho, Alto, topLeft2D, Angulo, detectarCuello);
|
||||
}
|
||||
|
||||
public void UpdateOrCreateLine(simGuia simGuia, Rectangle wpfRect)
|
||||
|
@ -1777,39 +1747,6 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si las dimensiones de una barrera han cambiado
|
||||
/// </summary>
|
||||
protected bool HasBarrierDimensionsChanged(simBarrera barrier, float newWidth, float newHeight)
|
||||
{
|
||||
if (barrier == null) return true;
|
||||
|
||||
var newDimensions = new SimObjectDimensions
|
||||
{
|
||||
Width = newWidth,
|
||||
Height = newHeight,
|
||||
ObjectType = 2 // Barrier
|
||||
};
|
||||
|
||||
if (_lastKnownDimensions.TryGetValue(barrier, out var lastDimensions))
|
||||
{
|
||||
bool changed = !newDimensions.Equals(lastDimensions);
|
||||
if (changed)
|
||||
{
|
||||
_lastKnownDimensions[barrier] = newDimensions;
|
||||
System.Diagnostics.Debug.WriteLine($"[Dimensions] Barrier dimensions CHANGED: {newWidth}x{newHeight}");
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primera vez - consideramos como cambio
|
||||
_lastKnownDimensions[barrier] = newDimensions;
|
||||
System.Diagnostics.Debug.WriteLine($"[Dimensions] Barrier first time: {newWidth}x{newHeight}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica si las dimensiones de una guía han cambiado
|
||||
/// </summary>
|
||||
|
|
2179
Simulacion/BEPU.cs
2179
Simulacion/BEPU.cs
File diff suppressed because it is too large
Load Diff
|
@ -163,6 +163,26 @@ namespace CtrEditor.Simulacion
|
|||
// Sincronizar cada objeto simBase
|
||||
foreach (var simObj in simulationManager.Cuerpos)
|
||||
{
|
||||
// ✅ NUEVO: Caso especial para simBarrera (no tiene BodyHandle físico)
|
||||
if (simObj is simBarrera barrera)
|
||||
{
|
||||
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🔍 Procesando simBarrera...");
|
||||
if (simBaseToModelMap.ContainsKey(simObj))
|
||||
{
|
||||
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🔄 Actualizando visualización existente de barrera");
|
||||
// Actualizar visualización existente de barrera
|
||||
UpdateBarreraVisualization(barrera);
|
||||
}
|
||||
else
|
||||
{
|
||||
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🆕 Creando nueva visualización de barrera");
|
||||
// Crear nueva visualización de barrera
|
||||
CreateBarreraVisualization(barrera);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Para objetos con BodyHandle físico
|
||||
if (simObj == null || !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
|
||||
continue;
|
||||
|
||||
|
@ -193,15 +213,31 @@ namespace CtrEditor.Simulacion
|
|||
var simObj = kvp.Key;
|
||||
var model = kvp.Value;
|
||||
|
||||
// Verificar si el objeto aún está en la lista de cuerpos activos
|
||||
if (!simulationManager.Cuerpos.Contains(simObj) ||
|
||||
!simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
|
||||
// ✅ NUEVO: Caso especial para simBarrera (no tiene BodyHandle físico)
|
||||
if (simObj is simBarrera)
|
||||
{
|
||||
if (model != null)
|
||||
// Verificar si la barrera aún está en la lista de cuerpos activos
|
||||
if (!simulationManager.Cuerpos.Contains(simObj))
|
||||
{
|
||||
viewport3D.Children.Remove(model);
|
||||
if (model != null)
|
||||
{
|
||||
viewport3D.Children.Remove(model);
|
||||
}
|
||||
objectsToRemove.Add(simObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Para objetos con BodyHandle físico
|
||||
if (!simulationManager.Cuerpos.Contains(simObj) ||
|
||||
!simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
|
||||
{
|
||||
if (model != null)
|
||||
{
|
||||
viewport3D.Children.Remove(model);
|
||||
}
|
||||
objectsToRemove.Add(simObj);
|
||||
}
|
||||
objectsToRemove.Add(simObj);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,13 +924,13 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
else if (simObj is simBarrera)
|
||||
{
|
||||
// Material semi-transparente amarillo para barreras (haz de luz)
|
||||
var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0));
|
||||
yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz
|
||||
// ✅ MEJORADO: Material para haz de luz más realista
|
||||
var lightBeamBrush = new SolidColorBrush(Color.FromRgb(255, 255, 100)); // Amarillo cálido
|
||||
lightBeamBrush.Opacity = 0.6; // 60% opacidad para simular haz de luz visible
|
||||
return MaterialHelper.CreateMaterial(
|
||||
yellowBrush,
|
||||
specularPower: 50,
|
||||
ambient: 150
|
||||
lightBeamBrush,
|
||||
specularPower: 80, // Algo de brillo para simular luz
|
||||
ambient: 200 // Ambiente alto para que "emita" luz
|
||||
);
|
||||
}
|
||||
else if (simObj is simBotella)
|
||||
|
@ -1173,6 +1209,213 @@ namespace CtrEditor.Simulacion
|
|||
System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR subscribing to camera events: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea la visualización del haz de luz para simBarrera
|
||||
/// Muestra una línea semi-transparente que representa el RayCast
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera que usa RayCast para detección</param>
|
||||
private void CreateBarreraVisualization(simBarrera barrera)
|
||||
{
|
||||
try
|
||||
{
|
||||
var visual3D = CreateLightBeamVisualization(barrera);
|
||||
|
||||
if (visual3D != null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Visual3D creado, actualizando transformación...");
|
||||
|
||||
// Posicionar y orientar el haz de luz
|
||||
UpdateBarreraTransform(barrera, visual3D);
|
||||
|
||||
// Agregar a la vista 3D y asociar con simBase
|
||||
viewport3D.Children.Add(visual3D);
|
||||
simBaseToModelMap[barrera] = visual3D;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Haz de luz creado exitosamente y agregado al viewport");
|
||||
System.Diagnostics.Debug.WriteLine($" Total children en viewport: {viewport3D.Children.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ CreateLightBeamVisualization devolvió null");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR creando visualización: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($" Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza la visualización existente de simBarrera
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera a actualizar</param>
|
||||
private void UpdateBarreraVisualization(simBarrera barrera)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!simBaseToModelMap.TryGetValue(barrera, out var visual))
|
||||
return;
|
||||
|
||||
// Verificar si las dimensiones han cambiado
|
||||
var currentDimensions = GetBarreraDimensions(barrera);
|
||||
bool dimensionsChanged = false;
|
||||
|
||||
if (lastKnownDimensions.TryGetValue(barrera, out var lastDimensions))
|
||||
{
|
||||
dimensionsChanged = !currentDimensions.Equals(lastDimensions);
|
||||
}
|
||||
else
|
||||
{
|
||||
dimensionsChanged = true; // Primera vez
|
||||
}
|
||||
|
||||
// Si las dimensiones cambiaron, recrear la geometría
|
||||
if (dimensionsChanged)
|
||||
{
|
||||
RecreateBarreraGeometry(barrera, visual, currentDimensions);
|
||||
lastKnownDimensions[barrera] = currentDimensions;
|
||||
}
|
||||
|
||||
// Actualizar transformación (posición y rotación)
|
||||
UpdateBarreraTransform(barrera, visual);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR actualizando visualización: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea la geometría del haz de luz (línea semi-transparente)
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera para crear el haz</param>
|
||||
/// <returns>Modelo visual del haz de luz</returns>
|
||||
private ModelVisual3D CreateLightBeamVisualization(simBarrera barrera)
|
||||
{
|
||||
try
|
||||
{
|
||||
var meshBuilder = new MeshBuilder();
|
||||
|
||||
// Crear una línea delgada que representa el haz de luz
|
||||
var halfWidth = barrera.Width / 2f;
|
||||
var beamThickness = 0.005f; // 5mm de grosor para el haz
|
||||
var beamHeight = 0.02f; // 2cm de altura para que sea visible
|
||||
|
||||
// Puntos del haz de luz (de extremo a extremo)
|
||||
var startPoint = new Point3D(-halfWidth, 0, beamHeight / 2);
|
||||
var endPoint = new Point3D(halfWidth, 0, beamHeight / 2);
|
||||
|
||||
// Crear cilindro delgado para el haz principal
|
||||
meshBuilder.AddCylinder(startPoint, endPoint, beamThickness, 8, false, false);
|
||||
|
||||
// ✅ OPCIONAL: Agregar pequeñas esferas en los extremos para marcar emisor y receptor
|
||||
meshBuilder.AddSphere(new Point3D(-halfWidth, 0, 2 * beamThickness), beamThickness * 2, 6, 6); // Emisor
|
||||
//meshBuilder.AddSphere(new Point3D(halfWidth, 0, 2 * beamThickness), beamThickness * 2, 6, 6); // Receptor
|
||||
|
||||
// Material semi-transparente para simular haz de luz
|
||||
var material = GetMaterialForSimBase(barrera);
|
||||
var model = new GeometryModel3D(meshBuilder.ToMesh(), material);
|
||||
var visual = new ModelVisual3D { Content = model };
|
||||
|
||||
return visual;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR creando geometría del haz: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza la transformación (posición y rotación) de la barrera
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera a transformar</param>
|
||||
/// <param name="visual">Visual 3D a transformar</param>
|
||||
private void UpdateBarreraTransform(simBarrera barrera, ModelVisual3D visual)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transform = new Transform3DGroup();
|
||||
|
||||
// ✅ CORREGIDO: rotationZ ya está en grados WPF, no necesita conversión adicional
|
||||
transform.Children.Add(new RotateTransform3D(
|
||||
new AxisAngleRotation3D(new Vector3D(0, 0, 1), barrera.AngleRadians)));
|
||||
|
||||
// Traslación
|
||||
transform.Children.Add(new TranslateTransform3D(barrera.Position.X, barrera.Position.Y, barrera.Position.Z));
|
||||
|
||||
visual.Transform = transform;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR actualizando transformación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Obtiene las dimensiones de una barrera para detectar cambios
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera de la cual obtener dimensiones</param>
|
||||
/// <returns>Dimensiones de la barrera</returns>
|
||||
private ShapeDimensions GetBarreraDimensions(simBarrera barrera)
|
||||
{
|
||||
return new ShapeDimensions
|
||||
{
|
||||
Width = barrera.Width,
|
||||
Height = barrera.Height,
|
||||
Length = 0,
|
||||
Radius = 0,
|
||||
ShapeType = -3, // Tipo especial para barreras
|
||||
InnerRadius = 0,
|
||||
OuterRadius = 0,
|
||||
StartAngle = 0,
|
||||
EndAngle = 0
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Recrea la geometría de la barrera cuando cambian las dimensiones
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera a recrear</param>
|
||||
/// <param name="visual">Visual 3D a actualizar</param>
|
||||
/// <param name="dimensions">Nuevas dimensiones</param>
|
||||
private void RecreateBarreraGeometry(simBarrera barrera, ModelVisual3D visual, ShapeDimensions dimensions)
|
||||
{
|
||||
if (visual?.Content is not GeometryModel3D geometryModel)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var meshBuilder = new MeshBuilder();
|
||||
|
||||
// Recrear la geometría del haz con las nuevas dimensiones
|
||||
var halfWidth = dimensions.Width / 2f;
|
||||
var beamThickness = 0.005f;
|
||||
var beamHeight = 0.02f;
|
||||
|
||||
var startPoint = new Point3D(-halfWidth, 0, beamHeight / 2);
|
||||
var endPoint = new Point3D(halfWidth, 0, beamHeight / 2);
|
||||
|
||||
meshBuilder.AddCylinder(startPoint, endPoint, beamThickness, 8, false, false);
|
||||
meshBuilder.AddSphere(new Point3D(-halfWidth, 0, 2*beamThickness), beamThickness * 2, 6, 6);
|
||||
//meshBuilder.AddSphere(new Point3D(halfWidth, 0, 2*beamThickness), beamThickness * 2, 6, 6);
|
||||
|
||||
var newGeometry = meshBuilder.ToMesh();
|
||||
var newMaterial = GetMaterialForSimBase(barrera);
|
||||
|
||||
geometryModel.Geometry = newGeometry;
|
||||
geometryModel.Material = newMaterial;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Geometría recreada con ancho: {dimensions.Width}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR recreando geometría: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum CameraView
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
/// <summary>
|
||||
/// Clase centralizada para manejar todas las conversiones entre coordenadas WPF y BEPU
|
||||
/// WPF: Y hacia abajo, ángulos en sentido horario, Top-Left como referencia
|
||||
/// BEPU: Y hacia arriba, ángulos en sentido antihorario, Center como referencia
|
||||
/// </summary>
|
||||
public static class CoordinateConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Convierte ángulo de WPF a BEPU (invierte signo) - USO INTERNO
|
||||
/// </summary>
|
||||
private static float WpfAngleToBepuAngle(float wpfAngle)
|
||||
{
|
||||
return -wpfAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte ángulo de BEPU a WPF (invierte signo) - USO INTERNO
|
||||
/// </summary>
|
||||
private static float BepuAngleToWpfAngle(float bepuAngle)
|
||||
{
|
||||
return -bepuAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte posición Y de WPF a BEPU (invierte signo) - USO INTERNO
|
||||
/// </summary>
|
||||
internal static float WpfYToBepuY(float wpfY)
|
||||
{
|
||||
return -wpfY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte posición Y de BEPU a WPF (invierte signo) - RETORNA VALOR WPF
|
||||
/// </summary>
|
||||
public static float BepuYToWpfY(float bepuY)
|
||||
{
|
||||
return -bepuY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte Vector2 de WPF a BEPU (solo invierte Y) - USO INTERNO
|
||||
/// </summary>
|
||||
internal static Vector2 WpfToBepuVector2(Vector2 wpfVector)
|
||||
{
|
||||
return new Vector2(wpfVector.X, -wpfVector.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte Vector2 de BEPU a WPF (solo invierte Y) - RETORNA VALOR WPF
|
||||
/// </summary>
|
||||
public static Vector2 BepuToWpfVector2(Vector2 bepuVector)
|
||||
{
|
||||
return new Vector2(bepuVector.X, -bepuVector.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convierte Vector3 de BEPU a Vector2 WPF (proyección XY con Y invertida) - RETORNA VALOR WPF
|
||||
/// </summary>
|
||||
public static Vector2 BepuVector3ToWpfVector2(Vector3 bepuVector)
|
||||
{
|
||||
return new Vector2(bepuVector.X, -bepuVector.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula la posición del centro en coordenadas BEPU desde Top-Left WPF - USO INTERNO
|
||||
/// Maneja correctamente la rotación del objeto
|
||||
/// </summary>
|
||||
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;
|
||||
var offsetY = height / 2f;
|
||||
|
||||
// Convertir ángulo WPF a radianes para cálculos trigonométricos
|
||||
var angleRadians = simBase.GradosARadianes(wpfAngle);
|
||||
var cos = (float)Math.Cos(angleRadians);
|
||||
var sin = (float)Math.Sin(angleRadians);
|
||||
|
||||
// Rotar el offset alrededor del Top-Left usando el ángulo WPF
|
||||
var rotatedOffsetX = offsetX * cos - offsetY * sin;
|
||||
var rotatedOffsetY = offsetX * sin + offsetY * cos;
|
||||
|
||||
// Calcular nueva posición del centro manteniendo Top-Left fijo
|
||||
var centerX = wpfTopLeft.X + rotatedOffsetX;
|
||||
var centerY = wpfTopLeft.Y + rotatedOffsetY;
|
||||
|
||||
// Convertir a 3D con Y invertida para BEPU
|
||||
var bepuY = WpfYToBepuY(centerY);
|
||||
var result = new Vector3(centerX, bepuY, zPosition);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula la posición Top-Left WPF desde el centro BEPU - RETORNA VALOR WPF
|
||||
/// Maneja correctamente la rotación del objeto
|
||||
/// </summary>
|
||||
public static Vector2 CalculateWpfTopLeftFromBepuCenter(Vector3 bepuCenter, float width, float height, float wpfAngle)
|
||||
{
|
||||
// Convertir centro BEPU a WPF
|
||||
var wpfCenterX = bepuCenter.X;
|
||||
var wpfCenterY = BepuYToWpfY(bepuCenter.Y);
|
||||
|
||||
// Calcular el offset del centro al Top-Left (sin rotación)
|
||||
var offsetX = -width / 2f;
|
||||
var offsetY = -height / 2f;
|
||||
|
||||
// Convertir ángulo WPF a radianes para cálculos trigonométricos
|
||||
var angleRadians = simBase.GradosARadianes(wpfAngle);
|
||||
var cos = (float)Math.Cos(angleRadians);
|
||||
var sin = (float)Math.Sin(angleRadians);
|
||||
|
||||
// Rotar el offset usando el ángulo WPF
|
||||
var rotatedOffsetX = offsetX * cos - offsetY * sin;
|
||||
var rotatedOffsetY = offsetX * sin + offsetY * cos;
|
||||
|
||||
// Calcular Top-Left desde el centro
|
||||
var topLeftX = wpfCenterX + rotatedOffsetX;
|
||||
var topLeftY = wpfCenterY + rotatedOffsetY;
|
||||
|
||||
return new Vector2(topLeftX, topLeftY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea un Quaternion para BEPU desde un ángulo WPF - USO INTERNO
|
||||
/// </summary>
|
||||
internal static Quaternion CreateBepuQuaternionFromWpfAngle(float wpfAngle)
|
||||
{
|
||||
var bepuAngle = WpfAngleToBepuAngle(wpfAngle);
|
||||
return Quaternion.CreateFromAxisAngle(Vector3.UnitZ, simBase.GradosARadianes(bepuAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extrae el ángulo WPF desde un Quaternion BEPU - RETORNA VALOR WPF
|
||||
/// </summary>
|
||||
public static float ExtractWpfAngleFromBepuQuaternion(Quaternion bepuQuaternion)
|
||||
{
|
||||
// Extraer ángulo Z del quaternion
|
||||
var bepuAngleRadians = (float)Math.Atan2(
|
||||
2.0 * (bepuQuaternion.W * bepuQuaternion.Z + bepuQuaternion.X * bepuQuaternion.Y),
|
||||
1.0 - 2.0 * (bepuQuaternion.Y * bepuQuaternion.Y + bepuQuaternion.Z * bepuQuaternion.Z)
|
||||
);
|
||||
|
||||
var bepuAngleDegrees = simBase.RadianesAGrados(bepuAngleRadians);
|
||||
return BepuAngleToWpfAngle(bepuAngleDegrees);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza la posición de un body BEPU manteniendo su rotación actual - USO INTERNO
|
||||
/// </summary>
|
||||
internal static void UpdateBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition)
|
||||
{
|
||||
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||
{
|
||||
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||
bodyReference.Pose.Position = newBepuPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza la rotación de un body BEPU manteniendo su posición actual - USO INTERNO
|
||||
/// </summary>
|
||||
internal static void UpdateBepuBodyRotation(Simulation simulation, BodyHandle bodyHandle, float wpfAngle)
|
||||
{
|
||||
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||
{
|
||||
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||
bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza posición y rotación de un body BEPU simultáneamente - USO INTERNO
|
||||
/// </summary>
|
||||
internal static void UpdateBepuBodyPose(Simulation simulation, BodyHandle bodyHandle, Vector3 newBepuPosition, float wpfAngle)
|
||||
{
|
||||
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||
{
|
||||
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||
bodyReference.Pose.Position = newBepuPosition;
|
||||
bodyReference.Pose.Orientation = CreateBepuQuaternionFromWpfAngle(wpfAngle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la posición del centro en coordenadas BEPU - USO INTERNO
|
||||
/// </summary>
|
||||
internal static Vector3 GetBepuBodyPosition(Simulation simulation, BodyHandle bodyHandle)
|
||||
{
|
||||
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||
{
|
||||
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||
return bodyReference.Pose.Position;
|
||||
}
|
||||
return Vector3.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el ángulo WPF desde un body BEPU - RETORNA VALOR WPF
|
||||
/// </summary>
|
||||
public static float GetWpfAngleFromBepuBody(Simulation simulation, BodyHandle bodyHandle)
|
||||
{
|
||||
if (simulation != null && simulation.Bodies.BodyExists(bodyHandle))
|
||||
{
|
||||
var bodyReference = simulation.Bodies.GetBodyReference(bodyHandle);
|
||||
return ExtractWpfAngleFromBepuQuaternion(bodyReference.Pose.Orientation);
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ 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
|
||||
/// </summary>
|
||||
internal static float WpfDegreesToBepuRadians(float wpfDegrees)
|
||||
{
|
||||
return simBase.GradosARadianes(WpfAngleToBepuAngle(wpfDegrees));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ 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
|
||||
/// </summary>
|
||||
public static float BepuRadiansToWpfDegrees(float bepuRadians)
|
||||
{
|
||||
return BepuAngleToWpfAngle(simBase.RadianesAGrados(bepuRadians));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
using BepuPhysics;
|
||||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics.Trees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO: RayHitHandler personalizado para simBarrera
|
||||
/// Calcula la distancia mínima del rayo al centro de cada botella
|
||||
/// </summary>
|
||||
public struct BarreraRayHitHandler : IRayHitHandler
|
||||
{
|
||||
private simBarrera _barrera;
|
||||
private List<simBotella> _detectedBottles;
|
||||
private SimulationManagerBEPU _simulationManager;
|
||||
private float _minDistance;
|
||||
private bool _neckDetected;
|
||||
private bool _fullDetected;
|
||||
private Vector3 _rayOrigin;
|
||||
private Vector3 _rayDirection;
|
||||
|
||||
internal BarreraRayHitHandler(simBarrera barrera, SimulationManagerBEPU simulationManager, Vector3 rayOrigin, Vector3 rayDirection)
|
||||
{
|
||||
_barrera = barrera;
|
||||
_simulationManager = simulationManager;
|
||||
_detectedBottles = new List<simBotella>();
|
||||
_minDistance = float.MaxValue;
|
||||
_neckDetected = false;
|
||||
_fullDetected = false;
|
||||
_rayOrigin = rayOrigin;
|
||||
_rayDirection = rayDirection;
|
||||
}
|
||||
|
||||
public bool AllowTest(CollidableReference collidable)
|
||||
{
|
||||
// Solo testear botellas dinámicas
|
||||
if (collidable.Mobility == CollidableMobility.Dynamic)
|
||||
{
|
||||
var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella;
|
||||
return bottle != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool AllowTest(CollidableReference collidable, int childIndex)
|
||||
{
|
||||
// Para botellas (esferas), childIndex siempre es 0
|
||||
return AllowTest(collidable);
|
||||
}
|
||||
|
||||
public void OnRayHit(in RayData ray, ref float maximumT, float t, Vector3 normal, CollidableReference collidable, int childIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Obtener la botella que fue hit
|
||||
var bottle = _simulationManager?.GetSimBaseFromBodyHandle(collidable.BodyHandle) as simBotella;
|
||||
if (bottle == null) return;
|
||||
|
||||
// Obtener posición del centro de la botella
|
||||
var bottleCenter = bottle.GetPosition();
|
||||
|
||||
// ✅ CORREGIDO: Calcular la distancia mínima del rayo (línea) al centro de la botella
|
||||
var minDistanceToCenter = CalculateMinimumDistanceFromRayToPoint(_rayOrigin, _rayDirection, bottleCenter);
|
||||
|
||||
// Actualizar distancia mínima global
|
||||
if (minDistanceToCenter < _minDistance)
|
||||
{
|
||||
_minDistance = minDistanceToCenter;
|
||||
}
|
||||
|
||||
// ✅ CORREGIDO: Verificar detección completa usando distancia mínima del rayo al centro
|
||||
if (minDistanceToCenter <= bottle.Radius)
|
||||
{
|
||||
_fullDetected = true;
|
||||
if (!_detectedBottles.Contains(bottle))
|
||||
{
|
||||
_detectedBottles.Add(bottle);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORREGIDO: Verificar detección de cuello usando circunferencia imaginaria con radio/2
|
||||
if (_barrera.DetectNeck && minDistanceToCenter <= (bottle.Radius / 2f))
|
||||
{
|
||||
_neckDetected = true;
|
||||
}
|
||||
|
||||
// System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Hit: Botella={bottle.BodyHandle}, distanciaMinima={minDistanceToCenter:F3}, radio={bottle.Radius:F3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[BarreraRayHitHandler] Error en OnRayHit: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Calcula la distancia mínima de un rayo (línea) a un punto
|
||||
/// </summary>
|
||||
private float CalculateMinimumDistanceFromRayToPoint(Vector3 rayOrigin, Vector3 rayDirection, Vector3 point)
|
||||
{
|
||||
// Vector desde el origen del rayo al punto
|
||||
var rayToPoint = point - rayOrigin;
|
||||
|
||||
// Proyección del vector rayToPoint sobre la dirección del rayo
|
||||
var projection = Vector3.Dot(rayToPoint, rayDirection);
|
||||
|
||||
// Punto más cercano en el rayo al punto dado
|
||||
var closestPointOnRay = rayOrigin + rayDirection * projection;
|
||||
|
||||
// Distancia mínima del rayo al punto
|
||||
return Vector3.Distance(closestPointOnRay, point);
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Método para obtener los resultados del raycast
|
||||
public void GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List<simBotella> botellas)
|
||||
{
|
||||
luzCortada = _fullDetected;
|
||||
luzCortadaNeck = _neckDetected;
|
||||
distancia = _minDistance == float.MaxValue ? 0f : _minDistance;
|
||||
botellas = new List<simBotella>(_detectedBottles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Estructura para datos de barrera que se exponen a WPF
|
||||
/// </summary>
|
||||
public struct BarreraData
|
||||
{
|
||||
public bool LuzCortada;
|
||||
public bool LuzCortadaNeck;
|
||||
|
||||
public BarreraData(bool luzCortada, bool luzCortadaNeck)
|
||||
{
|
||||
LuzCortada = luzCortada;
|
||||
LuzCortadaNeck = luzCortadaNeck;
|
||||
}
|
||||
}
|
||||
|
||||
public class simBarrera : simBase
|
||||
{
|
||||
internal bool LuzCortada;
|
||||
internal bool LuzCortadaNeck;
|
||||
public List<simBotella> ListSimBotellaContact;
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
public bool DetectNeck { get; set; }
|
||||
|
||||
// ✅ NUEVO: Propiedades para raycast
|
||||
private SimulationManagerBEPU _simulationManager;
|
||||
|
||||
// ✅ CORREGIDO: Almacenar ángulo internamente como radianes BEPU (como simCurve)
|
||||
private float _angleRadians; // ✅ SIEMPRE en radianes BEPU (ya convertido desde WPF)
|
||||
|
||||
internal simBarrera(Simulation simulation, float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false, SimulationManagerBEPU simulationManager = null)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_simulationManager = simulationManager;
|
||||
Width = width;
|
||||
Height = height;
|
||||
DetectNeck = detectectNeck;
|
||||
ListSimBotellaContact = new List<simBotella>();
|
||||
|
||||
// ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos
|
||||
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||
}
|
||||
|
||||
internal void Update(float width, float height, Vector3 bepuPosition, float bepuRadians = 0)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
ListSimBotellaContact = new List<simBotella>();
|
||||
|
||||
// ✅ CREAR POSICIÓN SIN BODY FÍSICO usando parámetros BEPU internos
|
||||
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||
}
|
||||
|
||||
|
||||
// ✅ NUEVO: Variables para almacenar pose sin crear body físico
|
||||
private Vector3 _position;
|
||||
private Quaternion _orientation;
|
||||
private bool _poseInitialized = false;
|
||||
|
||||
// ✅ NUEVO: Propiedad pública para acceder a la posición
|
||||
public Vector3 Position
|
||||
{
|
||||
get => _poseInitialized ? _position : Vector3.Zero;
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
_poseInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Propiedad pública para acceder al ángulo en radianes BEPU
|
||||
public float AngleRadians
|
||||
{
|
||||
get => _poseInitialized ? _angleRadians : 0f;
|
||||
set
|
||||
{
|
||||
_angleRadians = value;
|
||||
UpdateOrientationFromInternalAngle();
|
||||
_poseInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Método interno para actualizar orientación desde ángulo interno
|
||||
private void UpdateOrientationFromInternalAngle()
|
||||
{
|
||||
// ✅ CREAR QUATERNION DESDE RADIANES BEPU INTERNOS
|
||||
_orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, _angleRadians);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ INTERNO: Actualiza posición y rotación desde parámetros BEPU internos
|
||||
/// </summary>
|
||||
internal void Update(Vector3 bepuPosition, float bepuRadians)
|
||||
{
|
||||
Position = bepuPosition;
|
||||
AngleRadians = bepuRadians;
|
||||
}
|
||||
|
||||
public void SetDimensions(float width, float height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ INTERNO: Crea la posición usando parámetros BEPU internos
|
||||
/// </summary>
|
||||
internal void CreatePosition(float width, float height, Vector3 bepuPosition, float bepuRadians)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
// ✅ ALMACENAR DIRECTAMENTE parámetros BEPU internos
|
||||
Position = bepuPosition;
|
||||
AngleRadians = bepuRadians;
|
||||
|
||||
// ✅ NO CREAR BODY FÍSICO - solo almacenar datos
|
||||
_bodyCreated = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO: Realiza raycast para detectar botellas usando coordenadas BEPU internas
|
||||
/// </summary>
|
||||
public void PerformRaycast()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Resetear flags
|
||||
LuzCortada = false;
|
||||
LuzCortadaNeck = false;
|
||||
ListSimBotellaContact?.Clear();
|
||||
|
||||
// Validar que tenemos simulación y manager
|
||||
if (_simulation == null || _simulationManager == null || !_poseInitialized)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("[PerformRaycast] Simulación, manager o pose no inicializados");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ CORREGIDO: Usar coordenadas BEPU internas para cálculos
|
||||
var bepuPosition = Position;
|
||||
var Orientation = _orientation;
|
||||
|
||||
// ✅ CORREGIDO: Crear puntos del ray a la altura correcta
|
||||
var halfWidth = Width / 2f;
|
||||
var rayStartLocal = new Vector3(-halfWidth, 0, 0);
|
||||
var rayEndLocal = new Vector3(halfWidth, 0, 0);
|
||||
|
||||
// ✅ CORREGIDO: Ajustar la posición de la barrera para que el ray esté a la altura correcta
|
||||
var barreraPosition = new Vector3(bepuPosition.X, bepuPosition.Y, bepuPosition.Z);
|
||||
|
||||
// ✅ TRANSFORMAR PUNTOS LOCALES A COORDENADAS MUNDIALES A LA ALTURA CORRECTA
|
||||
var worldStart = barreraPosition + Vector3.Transform(rayStartLocal, Orientation);
|
||||
var worldEnd = barreraPosition + Vector3.Transform(rayEndLocal, Orientation);
|
||||
|
||||
// ✅ CALCULAR DIRECCIÓN Y DISTANCIA DEL RAY
|
||||
var rayDirection = worldEnd - worldStart;
|
||||
var rayDistance = rayDirection.Length();
|
||||
|
||||
if (rayDistance < 0.001f)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("[PerformRaycast] Ray demasiado corto");
|
||||
return;
|
||||
}
|
||||
|
||||
rayDirection = Vector3.Normalize(rayDirection);
|
||||
|
||||
// ✅ CORREGIDO: CREAR HANDLER PARA PROCESAR HITS con parámetros correctos
|
||||
var rayHandler = new BarreraRayHitHandler(this, _simulationManager, worldStart, rayDirection);
|
||||
|
||||
// ✅ REALIZAR RAYCAST
|
||||
_simulation.RayCast(worldStart, rayDirection, rayDistance, ref rayHandler);
|
||||
|
||||
// ✅ OBTENER RESULTADOS
|
||||
rayHandler.GetResults(out bool luzCortada, out bool luzCortadaNeck, out float distancia, out List<simBotella> botellas);
|
||||
|
||||
// ✅ ASIGNAR RESULTADOS
|
||||
LuzCortada = luzCortada;
|
||||
LuzCortadaNeck = luzCortadaNeck;
|
||||
|
||||
if (ListSimBotellaContact != null)
|
||||
{
|
||||
ListSimBotellaContact.Clear();
|
||||
ListSimBotellaContact.AddRange(botellas);
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[PerformRaycast] {worldStart} - {worldEnd} - {rayDistance} - {rayDirection} ");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PerformRaycast] Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ INTERNO: Métodos Create que trabajan solo con parámetros BEPU internos
|
||||
/// </summary>
|
||||
internal void Create(float width, float height, Vector3 bepuPosition, float bepuRadians = 0, bool detectectNeck = false)
|
||||
{
|
||||
CreatePosition(width, height, bepuPosition, bepuRadians);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ OVERRIDE: RemoverBody no hace nada porque no hay body físico
|
||||
/// </summary>
|
||||
public new void RemoverBody()
|
||||
{
|
||||
// ✅ NO HAY BODY FÍSICO QUE REMOVER
|
||||
_bodyCreated = false;
|
||||
_poseInitialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
public class simBase
|
||||
{
|
||||
public BodyHandle BodyHandle { get; protected set; }
|
||||
public Simulation _simulation;
|
||||
protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo
|
||||
protected SimulationManagerBEPU _simulationManager; // ✅ NUEVO: Referencia al manager
|
||||
|
||||
// ✅ CORREGIDO: Restaurar factor de conversión correcto
|
||||
public const float SPEED_CONVERSION_FACTOR = 1.55f; // Factor de conversión de velocidad interna a m/s - Para LinearAxisMotor es 0.5f
|
||||
|
||||
// Constantes para las posiciones Z de los objetos 3D
|
||||
public const float zPos_Transporte = 0f; // Z de la parte baja
|
||||
public const float zAltura_Transporte = 0.1f; // Altura del transporte sobre zPos
|
||||
|
||||
public const float zPos_Guia = 0.05f; // Z de la parte baja
|
||||
public const float zAltura_Guia = 0.20f; // Altura de la guía sobre zPos
|
||||
public const float zPos_Barrera = zPos_Transporte + zAltura_Transporte + 0.05f; // Z de la parte baja - 0.1 Altura Botella
|
||||
public const float zPos_Descarte = 0.1f; // Z de la parte baja
|
||||
|
||||
// Constantes para configuración
|
||||
public const float zPos_Curve = zPos_Transporte+ zAltura_Transporte; // Z de la parte alta de la curva
|
||||
|
||||
|
||||
public void RemoverBody()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Solo intentar remover si realmente hemos creado un cuerpo antes
|
||||
if (_bodyCreated && _simulation != null && _simulation.Bodies != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
_simulation.Bodies.Remove(BodyHandle);
|
||||
_bodyCreated = false; // Marcar como no creado después de remover
|
||||
//System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ✅ Body eliminado: {BodyHandle}");
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ⚠️ Body no existe o no creado: {BodyHandle}");
|
||||
//}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[simBase.RemoverBody] ❌ ERROR: {ex.Message}");
|
||||
_bodyCreated = false; // Marcar como no creado en caso de error
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Cambia la forma de un body existente, limpiando la forma anterior para evitar memory leaks
|
||||
/// </summary>
|
||||
protected void ChangeBodyShape(TypedIndex newShapeIndex)
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
|
||||
// ✅ CRÍTICO: Obtener la forma anterior para limpiarla del pool de shapes
|
||||
var oldShapeIndex = bodyReference.Collidable.Shape;
|
||||
|
||||
// Cambiar a la nueva forma
|
||||
_simulation.Bodies.SetShape(BodyHandle, newShapeIndex);
|
||||
|
||||
// ✅ CRÍTICO: Limpiar la forma anterior del pool para evitar memory leaks
|
||||
// Nota: Solo limpiar si es diferente (para evitar limpiar la forma que acabamos de asignar)
|
||||
if (oldShapeIndex.Packed != newShapeIndex.Packed)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simulation.Shapes.RemoveAndDispose(oldShapeIndex, _simulation.BufferPool);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[simBase] Warning: Could not dispose old shape: {ex.Message}");
|
||||
// Continuar - esto no es crítico para la funcionalidad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float Min(float Value, float Min = 0.01f)
|
||||
{
|
||||
return Math.Max(Value, Min);
|
||||
}
|
||||
|
||||
public static float GradosARadianes(float grados)
|
||||
{
|
||||
return grados * (float)Math.PI / 180f;
|
||||
}
|
||||
|
||||
public static float RadianesAGrados(float radianes)
|
||||
{
|
||||
return radianes * 180f / (float)Math.PI;
|
||||
}
|
||||
|
||||
public void SetPosition(float x, float y, float z = 0)
|
||||
{
|
||||
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, new Vector3(x, y, z));
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 wpfPosition)
|
||||
{
|
||||
// Mantener la coordenada Z actual para preservar la altura del objeto
|
||||
var currentBepuPosition = CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
|
||||
var newBepuPosition = new Vector3(wpfPosition.X, CoordinateConverter.WpfYToBepuY(wpfPosition.Y), currentBepuPosition.Z);
|
||||
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, newBepuPosition);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector3 bepuPosition)
|
||||
{
|
||||
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, bepuPosition);
|
||||
}
|
||||
|
||||
public Vector3 GetPosition()
|
||||
{
|
||||
return CoordinateConverter.GetBepuBodyPosition(_simulation, BodyHandle);
|
||||
}
|
||||
|
||||
public void SetRotation(float wpfAngle)
|
||||
{
|
||||
CoordinateConverter.UpdateBepuBodyRotation(_simulation, BodyHandle, wpfAngle);
|
||||
}
|
||||
|
||||
public float GetRotationZ()
|
||||
{
|
||||
return CoordinateConverter.GetWpfAngleFromBepuBody(_simulation, BodyHandle);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics.Constraints;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
||||
public class simBotella : simBase
|
||||
{
|
||||
public float Radius;
|
||||
public float Height; // Altura para la visualización del cilindro en Helix
|
||||
private float _mass;
|
||||
public bool Descartar = false;
|
||||
public int isOnTransports;
|
||||
public List<simBase> ListOnTransports;
|
||||
public bool isRestricted;
|
||||
public bool isNoMoreRestricted;
|
||||
|
||||
public simTransporte ConveyorRestrictedTo;
|
||||
public float OverlapPercentage;
|
||||
public float _neckRadius;
|
||||
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
|
||||
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
|
||||
|
||||
// ✅ NUEVO SISTEMA SIMPLIFICADO: Motor dinámico que se crea solo cuando es necesario
|
||||
public ConstraintHandle CurrentMotor { get; private set; } = default;
|
||||
public ConstraintHandle CurrentDistanceLimit { get; private set; } = default; // ✅ NUEVO: Para curvas
|
||||
public simBase CurrentMotorTarget { get; private set; } = null; // Transporte o curva actual
|
||||
public bool _hasMotor = false; // ✅ BANDERA PÚBLICA para acceso desde callbacks
|
||||
public bool HasMotor => _hasMotor;
|
||||
|
||||
// ✅ NUEVAS PROPIEDADES para el motor dinámico
|
||||
public Vector3 CurrentDirection { get; private set; } = Vector3.UnitX; // Dirección por defecto (1,0,0)
|
||||
public float CurrentSpeed { get; private set; } = 0f; // Velocidad por defecto
|
||||
public bool IsOnElement { get; private set; } = false; // Si está sobre un elemento activo
|
||||
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
public simBotella(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_deferredActions = deferredActions;
|
||||
Radius = diameter / 2f;
|
||||
Height = diameter; // Altura igual al diámetro para mantener proporciones similares
|
||||
_mass = mass;
|
||||
_neckRadius = neckRadius;
|
||||
ListOnTransports = new List<simBase>();
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
|
||||
Create(position3D);
|
||||
|
||||
// ✅ ELIMINADO: No crear motor permanente - se creará cuando sea necesario
|
||||
}
|
||||
|
||||
public float CenterX
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetPosition().X;
|
||||
}
|
||||
set
|
||||
{
|
||||
var pos = GetPosition();
|
||||
SetPosition(value, pos.Y, pos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public float CenterY
|
||||
{
|
||||
get
|
||||
{
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
return CoordinateConverter.BepuYToWpfY(GetPosition().Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
var pos = GetPosition();
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get
|
||||
{
|
||||
var pos3D = GetPosition();
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
return CoordinateConverter.BepuVector3ToWpfVector2(pos3D);
|
||||
}
|
||||
set
|
||||
{
|
||||
// Mantener la Z actual, solo cambiar X, Y
|
||||
var currentPos = GetPosition();
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public float Mass
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
return 1f / bodyReference.LocalInertia.InverseMass;
|
||||
}
|
||||
return _mass;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mass = value;
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
// Usar esfera simple - sin complejidad de inercia personalizada
|
||||
var sphere = new Sphere(Radius);
|
||||
var inertia = sphere.ComputeInertia(value);
|
||||
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// Usar ESFERA en BEPU para simplicidad matemática y eficiencia
|
||||
var sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
|
||||
// Inercia estándar de esfera - sin complejidad adicional
|
||||
var inertia = sphere.ComputeInertia(_mass);
|
||||
|
||||
// NUNCA DORMIR - Valor negativo significa que las botellas JAMÁS entran en sleep mode
|
||||
// Esto es crítico para detección continua de barreras, transportes y descartes
|
||||
var activityDescription = new BodyActivityDescription(-1f); // -1 = NUNCA dormir
|
||||
|
||||
var bodyDescription = BodyDescription.CreateDynamic(
|
||||
new RigidPose(position),
|
||||
new BodyVelocity(),
|
||||
inertia, // Inercia estándar de esfera
|
||||
new CollidableDescription(shapeIndex, 0.001f),
|
||||
activityDescription
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea o actualiza el motor conectándolo al transporte/curva actual
|
||||
/// </summary>
|
||||
public void CreateOrUpdateMotor(simBase target, Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ VALIDAR DIRECCIÓN
|
||||
if (direction.Length() < 0.001f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VALIDAR QUE EL TARGET Y LA SIMULACIÓN EXISTAN
|
||||
if (target == null || _simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR SI NECESITAMOS CREAR O ACTUALIZAR EL MOTOR
|
||||
bool needsNewMotor = false;
|
||||
|
||||
if (!HasMotor)
|
||||
{
|
||||
// ✅ PRIMERA VEZ: Crear motor nuevo
|
||||
needsNewMotor = true;
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🆕 Creando motor nuevo para {target?.GetType().Name}");
|
||||
}
|
||||
else if (CurrentMotorTarget != target)
|
||||
{
|
||||
// ✅ CAMBIO DE OBJETO: Eliminar motor anterior y crear uno nuevo
|
||||
RemoveCurrentMotor();
|
||||
needsNewMotor = true;
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] 🔄 Cambiando motor de {CurrentMotorTarget?.GetType().Name} a {target?.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ✅ MISMO OBJETO: Solo actualizar velocidad
|
||||
UpdateMotorSpeed(direction, speed / simBase.SPEED_CONVERSION_FACTOR);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ CREAR NUEVO MOTOR SI ES NECESARIO
|
||||
if (needsNewMotor && target != null)
|
||||
{
|
||||
CreateMotorForTarget(target, direction, speed / simBase.SPEED_CONVERSION_FACTOR);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateOrUpdateMotor] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea un motor específico para un transporte o curva
|
||||
/// </summary>
|
||||
private void CreateMotorForTarget(simBase target, Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_simulation == null || _simulation.Solver == null || !_simulation.Bodies.BodyExists(BodyHandle) || target == null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Simulación, solver, body o target no disponible");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR QUE EL TARGET TENGA UN BODY VÁLIDO
|
||||
if (!_simulation.Bodies.BodyExists(target.BodyHandle))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ Target body no existe: {target.BodyHandle}");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ VERIFICAR QUE NO TENGA YA UN MOTOR VÁLIDO
|
||||
if (HasMotor && CurrentMotor.Value != 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ⚠️ Ya existe un motor válido: {CurrentMotor}");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
|
||||
var tangentDir = Vector3.Normalize(direction);
|
||||
|
||||
// ✅ CREAR MOTOR CONECTADO AL TARGET
|
||||
var motor = new LinearAxisMotor()
|
||||
{
|
||||
LocalOffsetA = Vector3.Zero, // Target
|
||||
LocalOffsetB = Vector3.Zero, // Botella
|
||||
LocalAxis = tangentDir, // ✅ CORREGIDO: Usar la dirección tangencial calculada
|
||||
TargetVelocity = speed, // ✅ CORREGIDO: Usar la velocidad directamente
|
||||
Settings = new MotorSettings(Math.Max(_mass * 20f, 8f), 4f)
|
||||
};
|
||||
|
||||
// ✅ CONECTAR BOTELLA CON EL TARGET (transporte o curva)
|
||||
CurrentMotor = _simulation.Solver.Add(target.BodyHandle, BodyHandle, motor);
|
||||
|
||||
CurrentMotorTarget = target;
|
||||
_hasMotor = true; // ✅ ESTABLECER BANDERA
|
||||
|
||||
//if (target is simCurve curva) {
|
||||
// // Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
|
||||
// var curveCenter = curva.CurveCenter;
|
||||
// var bottlePosition = GetPosition();
|
||||
|
||||
// var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
|
||||
// var radius = radiusVector.Length();
|
||||
|
||||
// if (radius > 1e-3f)
|
||||
// {
|
||||
|
||||
// // Calcular offsets locales
|
||||
// var localOffsetA = curveCenter; // Desde el centro de la curva hasta el punto de anclaje
|
||||
// var localOffsetB = Vector3.Zero; // ✅ SIMPLIFICADO: Conectar al centro de la botella
|
||||
|
||||
// var distanceLimit = new DistanceLimit()
|
||||
// {
|
||||
// LocalOffsetA = Vector3.Zero,
|
||||
// LocalOffsetB = Vector3.Zero,
|
||||
// MinimumDistance = radius- 4*Radius, // Distancia mínima = radio actual
|
||||
// MaximumDistance = radius+ 4*Radius, // Distancia máxima = radio actual (mantener distancia fija)
|
||||
// SpringSettings = new SpringSettings(30f, 0f)
|
||||
// };
|
||||
|
||||
// //CurrentDistanceLimit = _simulation.Solver.Add(target.BodyHandle, BodyHandle, distanceLimit);
|
||||
|
||||
// //System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget-Curve] 📏 DistanceLimit creado:");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Radio actual: {radius:F3}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Punto de anclaje: {anchorPoint}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetA (curva): {localOffsetA}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" LocalOffsetB (botella): {localOffsetB} (centro)");
|
||||
// //System.Diagnostics.Debug.WriteLine($" Distancia objetivo: {radius:F3}");
|
||||
// //System.Diagnostics.Debug.WriteLine($" DistanceLimit Handle: {CurrentDistanceLimit}");
|
||||
// }
|
||||
//}
|
||||
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
|
||||
CurrentDirection = direction;
|
||||
CurrentSpeed = speed;
|
||||
IsOnElement = Math.Abs(speed) > 0.001f;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ✅ Motor creado:");
|
||||
//System.Diagnostics.Debug.WriteLine($" Botella: {BodyHandle}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Target: {target.BodyHandle} ({target.GetType().Name})");
|
||||
//System.Diagnostics.Debug.WriteLine($" Motor Handle: {CurrentMotor} (Value: {CurrentMotor.Value})");
|
||||
//System.Diagnostics.Debug.WriteLine($" Dirección: {direction}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Velocidad efectiva: {effectiveSpeed:F3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateMotorForTarget] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza solo la velocidad del motor existente
|
||||
/// </summary>
|
||||
private void UpdateMotorSpeed(Vector3 direction, float speed)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!HasMotor || _simulation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ NORMALIZAR LA DIRECCIÓN TANGENCIAL
|
||||
var tangentDir = Vector3.Normalize(direction);
|
||||
|
||||
// ✅ OBTENER LA DESCRIPCIÓN ACTUAL DEL MOTOR
|
||||
_simulation.Solver.GetDescription(CurrentMotor, out LinearAxisMotor motor);
|
||||
|
||||
// ✅ ACTUALIZAR DIRECCIÓN Y VELOCIDAD
|
||||
motor.LocalAxis = tangentDir;
|
||||
motor.TargetVelocity = speed;
|
||||
|
||||
// ✅ ACTUALIZAR EL MOTOR EN EL SOLVER
|
||||
_simulation.Solver.ApplyDescription(CurrentMotor, motor);
|
||||
|
||||
// ✅ ACTUALIZAR PROPIEDADES INTERNAS
|
||||
CurrentDirection = direction;
|
||||
CurrentSpeed = speed;
|
||||
IsOnElement = Math.Abs(speed) > 0.001f;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] 🔄 Velocidad actualizada: {effectiveSpeed:F3}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[UpdateMotorSpeed] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Elimina el motor actual
|
||||
/// </summary>
|
||||
public void RemoveCurrentMotor()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (HasMotor && _simulation != null && _simulation.Solver != null)
|
||||
{
|
||||
// ✅ VERIFICAR QUE EL MOTOR EXISTE ANTES DE ELIMINARLO
|
||||
if (CurrentMotor.Value != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simulation.Solver.Remove(CurrentMotor);
|
||||
//System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ Motor eliminado: {CurrentMotor}");
|
||||
}
|
||||
catch (Exception removeEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando motor {CurrentMotor}: {removeEx.Message}");
|
||||
// Continuar con la limpieza incluso si falla la eliminación
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Motor ya eliminado o inválido: {CurrentMotor}");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Eliminar DistanceLimit si existe
|
||||
if (CurrentDistanceLimit.Value != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simulation.Solver.Remove(CurrentDistanceLimit);
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] 🗑️ DistanceLimit eliminado: {CurrentDistanceLimit}");
|
||||
}
|
||||
catch (Exception removeEx)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ⚠️ Error eliminando DistanceLimit {CurrentDistanceLimit}: {removeEx.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[RemoveCurrentMotor] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// ✅ LIMPIAR REFERENCIAS SIEMPRE
|
||||
CurrentMotor = default;
|
||||
CurrentDistanceLimit = default; // ✅ NUEVO: Limpiar DistanceLimit
|
||||
CurrentMotorTarget = null;
|
||||
_hasMotor = false; // ✅ LIMPIAR BANDERA
|
||||
CurrentDirection = Vector3.UnitX;
|
||||
CurrentSpeed = 0f;
|
||||
IsOnElement = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Detiene el motor (velocidad 0) pero mantiene la conexión
|
||||
/// </summary>
|
||||
public void StopMotor()
|
||||
{
|
||||
UpdateMotorSpeed(CurrentDirection, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Limpia las restricciones asociadas a un elemento específico
|
||||
/// Solo limpia los datos internos de simBotella, no elimina las restricciones de BEPU
|
||||
/// </summary>
|
||||
/// <param name="target">Elemento (simTransporte o simCurve) del cual limpiar las restricciones</param>
|
||||
public void CleanRestrictions(simBase target)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ VERIFICAR SI EL TARGET COINCIDE CON EL ACTUAL
|
||||
if (CurrentMotorTarget == target)
|
||||
{
|
||||
// ✅ LIMPIAR SOLO LOS DATOS INTERNOS (no eliminar restricciones de BEPU)
|
||||
CurrentMotorTarget = null;
|
||||
_hasMotor = false; // ✅ CRÍTICO: Limpiar el flag del motor
|
||||
CurrentDirection = Vector3.UnitX;
|
||||
CurrentSpeed = 0f;
|
||||
IsOnElement = false;
|
||||
|
||||
// ✅ NO LIMPIAR CurrentMotor ni CurrentDistanceLimit - BEPU los maneja automáticamente
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ✅ Restricciones limpiadas para {target?.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ⚠️ Target no coincide: actual={CurrentMotorTarget?.GetType().Name}, solicitado={target?.GetType().Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CleanRestrictions] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void SetDiameter(float diameter)
|
||||
{
|
||||
Radius = diameter / 2f;
|
||||
Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes
|
||||
|
||||
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHeight(float height)
|
||||
{
|
||||
Height = height;
|
||||
|
||||
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var sphere = new Sphere(Radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMass(float mass)
|
||||
{
|
||||
Mass = mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limita la rotación de la botella solo al plano XY (siempre "de pie")
|
||||
/// Esto simplifica enormemente la simulación y es más realista para botellas
|
||||
/// </summary>
|
||||
public void LimitRotationToXYPlane()
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
|
||||
// Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y
|
||||
var currentOrientation = bodyReference.Pose.Orientation;
|
||||
|
||||
// Convertir a ángulo en Z solamente
|
||||
var rotationZ = GetRotationZ();
|
||||
|
||||
// Crear nueva orientación solo con rotación en Z (botella siempre "de pie")
|
||||
var correctedOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
|
||||
|
||||
// Aplicar la orientación corregida
|
||||
bodyReference.Pose.Orientation = correctedOrientation;
|
||||
|
||||
// También limitar la velocidad angular a solo rotación en Z
|
||||
var angularVelocity = bodyReference.Velocity.Angular;
|
||||
bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyLinearVelocity(Vector3 velocity)
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
bodyReference.Velocity.Linear = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetLinearVelocity()
|
||||
{
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
return bodyReference.Velocity.Linear;
|
||||
}
|
||||
return Vector3.Zero;
|
||||
}
|
||||
|
||||
public void CenterFixtureOnConveyor()
|
||||
{
|
||||
// Implementar lógica de centrado si es necesario
|
||||
}
|
||||
|
||||
public bool IsOnAnyTransport()
|
||||
{
|
||||
return isOnTransports > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints
|
||||
/// </summary>
|
||||
public new void RemoverBody()
|
||||
{
|
||||
base.RemoverBody();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class simCurve : simBase
|
||||
{
|
||||
private float _innerRadius;
|
||||
private float _outerRadius;
|
||||
private float _startAngle; // ✅ SIEMPRE en radianes BEPU
|
||||
private float _endAngle; // ✅ SIEMPRE en radianes BEPU
|
||||
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
|
||||
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
// ✅ NUEVO: Almacenar el centro real de la curva
|
||||
private Vector3 _curveCenter;
|
||||
|
||||
// ✅ SIMPLIFICADO: Propiedades esenciales únicamente
|
||||
public List<simBotella> BottlesOnCurve { get; private set; } = new List<simBotella>();
|
||||
|
||||
// ✅ EVENTO para actualización de motores
|
||||
public event Action<simCurve> OnSpeedChanged;
|
||||
|
||||
// ✅ NUEVO: Propiedad para velocidad convertida (similar a simTransporte)
|
||||
public float SpeedMetersPerSecond { get; private set; }
|
||||
|
||||
|
||||
// ✅ NUEVO: Almacenar triángulos creados para acceso directo
|
||||
private Triangle[] _storedTriangles;
|
||||
|
||||
public float InnerRadius => _innerRadius;
|
||||
public float OuterRadius => _outerRadius;
|
||||
public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF
|
||||
public float EndAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_endAngle); // Convertir de radianes BEPU internos a grados WPF
|
||||
|
||||
// ✅ NUEVO: Propiedad para acceder al centro real de la curva
|
||||
public Vector3 CurveCenter => _curveCenter;
|
||||
|
||||
public simCurve(Simulation simulation, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_deferredActions = deferredActions;
|
||||
_simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia
|
||||
_innerRadius = innerRadius;
|
||||
_outerRadius = outerRadius;
|
||||
// ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes
|
||||
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||
|
||||
// ✅ NUEVO: Calcular y almacenar el centro real de la curva
|
||||
var curveSize = outerRadius * 2f;
|
||||
var zPosition = zPos_Curve;
|
||||
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(topLeft, curveSize, curveSize, 0f, zPosition);
|
||||
|
||||
// ✅ SIMPLIFICADO: Crear la curva directamente
|
||||
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
|
||||
|
||||
// ✅ NUEVO: Inicializar SpeedMetersPerSecond
|
||||
SpeedMetersPerSecond = Speed / simBase.SPEED_CONVERSION_FACTOR;
|
||||
}
|
||||
|
||||
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed; // Velocidad angular directa (sin inversión)
|
||||
SpeedMetersPerSecond = Speed / simBase.SPEED_CONVERSION_FACTOR; // ✅ NUEVO: Calcular velocidad convertida
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Configura la velocidad de la curva en metros por segundo
|
||||
/// Valores positivos mueven en sentido horario, negativos en sentido antihorario
|
||||
/// </summary>
|
||||
/// <param name="speedMeterPerSecond">Velocidad en m/s (típicamente entre -5.0 y 5.0)</param>
|
||||
public void SetCurveSpeed(float speedMeterPerSecond)
|
||||
{
|
||||
SetSpeed(speedMeterPerSecond * simBase.SPEED_CONVERSION_FACTOR);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Detiene completamente la curva
|
||||
/// </summary>
|
||||
public void StopCurve()
|
||||
{
|
||||
SetSpeed(0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Invierte la dirección de la curva manteniendo la misma velocidad
|
||||
/// </summary>
|
||||
public void ReverseCurve()
|
||||
{
|
||||
SetSpeed(-Speed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
|
||||
/// </summary>
|
||||
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float innerRadius, float outerRadius, float startAngle, float endAngle)
|
||||
{
|
||||
// Actualizar parámetros de la curva
|
||||
_innerRadius = innerRadius;
|
||||
_outerRadius = outerRadius;
|
||||
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
||||
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||
|
||||
// ✅ NUEVO: Actualizar el centro real de la curva
|
||||
var curveSize = outerRadius * 2f;
|
||||
var zPosition = zPos_Curve;
|
||||
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||
|
||||
Create(_curveCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza posición desde Top-Left WPF manteniendo parámetros actuales
|
||||
/// </summary>
|
||||
internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
|
||||
{
|
||||
var curveSize = _outerRadius * 2f;
|
||||
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||
|
||||
Create(_curveCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
|
||||
/// </summary>
|
||||
internal Vector2 GetWpfTopLeft()
|
||||
{
|
||||
var curveSize = _outerRadius * 2f;
|
||||
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(_curveCenter, curveSize, curveSize, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO: Obtiene los triángulos reales creados para la curva
|
||||
/// Devuelve los triángulos en coordenadas locales para evitar transformación duplicada
|
||||
/// </summary>
|
||||
public Triangle[] GetRealBEPUTriangles()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] No hay triángulos almacenados");
|
||||
return new Triangle[0];
|
||||
}
|
||||
|
||||
// ✅ CORREGIDO: Devolver triángulos en coordenadas locales
|
||||
// La visualización 3D aplicará la transformación una sola vez
|
||||
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {_storedTriangles.Length} triángulos en coordenadas locales");
|
||||
return _storedTriangles;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Error: {ex.Message}");
|
||||
return new Triangle[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Obtiene los triángulos transformados a coordenadas mundiales (solo para debugging)
|
||||
/// </summary>
|
||||
public Triangle[] GetWorldBEPUTriangles()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
||||
{
|
||||
return new Triangle[0];
|
||||
}
|
||||
|
||||
var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles);
|
||||
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos en coordenadas mundiales");
|
||||
return worldTriangles;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Error: {ex.Message}");
|
||||
return new Triangle[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public new void RemoverBody()
|
||||
{
|
||||
// ✅ NUEVO: Limpiar restricciones de botellas conectadas antes de eliminar el cuerpo
|
||||
if (_simulationManager != null)
|
||||
{
|
||||
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
|
||||
foreach (var bottle in connectedBottles)
|
||||
{
|
||||
bottle.CleanRestrictions(this);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Limpiar triángulos almacenados
|
||||
_storedTriangles = null;
|
||||
|
||||
// ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único)
|
||||
base.RemoverBody();
|
||||
}
|
||||
|
||||
public void Create(float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 wpfTopLeft, float unused = 0)
|
||||
{
|
||||
// ✅ CORRIGIDO: Como Aether - startAngle y endAngle definen directamente el sector
|
||||
// No hay rotación separada del objeto
|
||||
|
||||
// Actualizar parámetros internos
|
||||
_innerRadius = innerRadius;
|
||||
_outerRadius = outerRadius;
|
||||
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
||||
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
||||
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
||||
|
||||
// ✅ NUEVO: Actualizar el centro real de la curva
|
||||
// Para curvas, el "tamaño" es el diámetro del radio exterior
|
||||
var curveSize = outerRadius * 2f;
|
||||
var zPosition = zPos_Curve;
|
||||
|
||||
// Calcular nueva posición del centro (sin rotación - el sector ya está definido por los ángulos)
|
||||
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
||||
|
||||
Create(_curveCenter); // Sin rotación adicional
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// ✅ SIMPLIFICADO: Crear superficie usando Triangle de BEPU directamente
|
||||
var triangles = CreateSimpleArcTriangles(_innerRadius, _outerRadius, _startAngle, _endAngle);
|
||||
|
||||
// ✅ ALMACENAR triángulos para acceso directo
|
||||
_storedTriangles = triangles;
|
||||
|
||||
// ✅ SIMPLIFICADO: Crear un solo cuerpo con múltiples Triangle shapes usando Mesh
|
||||
if (triangles.Length > 0)
|
||||
{
|
||||
// ✅ CREAR MESH CON LA API CORRECTA DE BEPU
|
||||
var triangleBuffer = new BepuUtilities.Memory.Buffer<Triangle>(triangles.Length, _simulation.BufferPool);
|
||||
for (int i = 0; i < triangles.Length; i++)
|
||||
{
|
||||
triangleBuffer[i] = triangles[i];
|
||||
}
|
||||
var mesh = new BepuPhysics.Collidables.Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
|
||||
var shapeIndex = _simulation.Shapes.Add(mesh);
|
||||
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(position),
|
||||
new CollidableDescription(shapeIndex, 0.001f), // ✅ SpeculativeMargin bajo para detección precisa
|
||||
new BodyActivityDescription(0.01f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[simCurve] Creado con {triangles.Length} triángulos reales almacenados");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MÉTODOS ELIMINADOS: CreateMainBody y CreateTriangleBody ya no son necesarios
|
||||
// La curva ahora se crea como un solo Mesh en el método Create simplificado
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
|
||||
/// Solo superficie superior, eliminando complejidad innecesaria
|
||||
/// Los triángulos se crean en coordenadas locales (Z=0) y la posición del cuerpo los ubica correctamente
|
||||
/// </summary>
|
||||
/// <param name="innerRadius">Radio interno del arco</param>
|
||||
/// <param name="outerRadius">Radio externo del arco</param>
|
||||
/// <param name="startAngle">Ángulo inicial en radianes BEPU</param>
|
||||
/// <param name="endAngle">Ángulo final en radianes BEPU</param>
|
||||
/// <returns>Array de triángulos nativos de BEPU en coordenadas locales</returns>
|
||||
private Triangle[] CreateSimpleArcTriangles(float innerRadius, float outerRadius, float startAngle, float endAngle)
|
||||
{
|
||||
var triangles = new List<Triangle>();
|
||||
|
||||
// ✅ SIMPLIFICADO: Menos segmentos, menos complejidad
|
||||
float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
|
||||
int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos
|
||||
float angleStep = (endAngle - startAngle) / segments;
|
||||
|
||||
// ✅ SIMPLIFICADO: Sin inversión compleja de ángulos
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
float angle1 = startAngle + i * angleStep;
|
||||
float angle2 = startAngle + (i + 1) * angleStep;
|
||||
|
||||
// ✅ COORDENADAS LOCALES: Triángulos en Z=0, el cuerpo los posiciona correctamente
|
||||
var inner1 = new Vector3(innerRadius * (float)Math.Cos(angle1), innerRadius * (float)Math.Sin(angle1), 0);
|
||||
var outer1 = new Vector3(outerRadius * (float)Math.Cos(angle1), outerRadius * (float)Math.Sin(angle1), 0);
|
||||
var inner2 = new Vector3(innerRadius * (float)Math.Cos(angle2), innerRadius * (float)Math.Sin(angle2), 0);
|
||||
var outer2 = new Vector3(outerRadius * (float)Math.Cos(angle2), outerRadius * (float)Math.Sin(angle2), 0);
|
||||
|
||||
// ✅ USAR Triangle NATIVO DE BEPU (solo superficie superior)
|
||||
triangles.Add(new Triangle(inner1, outer1, outer2));
|
||||
triangles.Add(new Triangle(inner1, outer2, inner2));
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[CreateSimpleArcTriangles] Creados {triangles.Count} triángulos en coordenadas locales");
|
||||
return triangles.ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ✅ MÉTODOS ELIMINADOS: Los cálculos tangenciales complejos ya no son necesarios
|
||||
// AngularAxisMotor maneja automáticamente la rotación en curvas
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
||||
public class simDescarte : simBase
|
||||
{
|
||||
private float _radius;
|
||||
private List<Action> _deferredActions;
|
||||
public List<simBotella> ListSimBotellaContact;
|
||||
|
||||
public simDescarte(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_deferredActions = deferredActions;
|
||||
_radius = diameter / 2f;
|
||||
ListSimBotellaContact = new List<simBotella>();
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius);
|
||||
Create(position3D);
|
||||
}
|
||||
|
||||
public float Radius
|
||||
{
|
||||
get { return _radius; }
|
||||
set
|
||||
{
|
||||
_radius = Math.Max(value, 0.01f); // Mínimo 1cm
|
||||
// Recrear el cuerpo con el nuevo radio
|
||||
var currentPos = GetPosition();
|
||||
Create(currentPos);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDiameter(float diameter)
|
||||
{
|
||||
Radius = diameter / 2f;
|
||||
}
|
||||
|
||||
public float GetDiameter()
|
||||
{
|
||||
return _radius * 2f;
|
||||
}
|
||||
|
||||
public void Create(Vector2 position)
|
||||
{
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), zPos_Descarte + _radius);
|
||||
Create(position3D);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza posición usando coordenadas WPF apropiadas
|
||||
/// </summary>
|
||||
internal void UpdateFromWpfCenter(Vector2 wpfCenter)
|
||||
{
|
||||
var position3D = new Vector3(wpfCenter.X, CoordinateConverter.WpfYToBepuY(wpfCenter.Y), zPos_Descarte + _radius);
|
||||
|
||||
// Actualizar solo posición manteniendo orientación
|
||||
CoordinateConverter.UpdateBepuBodyPosition(_simulation, BodyHandle, position3D);
|
||||
}
|
||||
|
||||
private void Create(Vector3 position)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// Crear esfera sensor para detección
|
||||
var sphere = new BepuPhysics.Collidables.Sphere(_radius);
|
||||
var shapeIndex = _simulation.Shapes.Add(sphere);
|
||||
|
||||
// Crear como SENSOR (Kinematic con speculative margin 0 para detección pura)
|
||||
// Configurar actividad para NUNCA dormir - los sensores deben estar siempre activos
|
||||
var activityDescription = new BodyActivityDescription(-1f); // -1 = nunca dormir
|
||||
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(position),
|
||||
new CollidableDescription(shapeIndex, 0f), // Speculative margin 0 para sensores
|
||||
activityDescription
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
||||
public class simGuia : simBase
|
||||
{
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
// Propiedades para acceder a las dimensiones del objeto WPF
|
||||
public float GuideThickness { get; set; } // Espesor de la guía
|
||||
|
||||
// ✅ NUEVO: Agregar propiedades Width y Height para almacenar dimensiones reales
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
|
||||
public simGuia(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_deferredActions = deferredActions;
|
||||
|
||||
// ✅ NUEVO: Almacenar dimensiones reales
|
||||
Width = width;
|
||||
Height = height;
|
||||
GuideThickness = height; // Mantener compatibilidad
|
||||
|
||||
Create(width, height, topLeft, angle);
|
||||
}
|
||||
|
||||
public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||
Width = width;
|
||||
Height = height;
|
||||
GuideThickness = height; // Mantener compatibilidad
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||
|
||||
// ✅ CRÍTICO: Crear el Box con las dimensiones correctas almacenadas
|
||||
var box = new Box(Width, GuideThickness, zAltura_Guia);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(bepuCenter, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
|
||||
new CollidableDescription(shapeIndex, 0.1f),
|
||||
new BodyActivityDescription(0.01f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true;
|
||||
}
|
||||
|
||||
// Método para actualizar las propiedades desde el objeto WPF
|
||||
public void UpdateProperties(float ancho, float altoGuia, float angulo)
|
||||
{
|
||||
// ✅ CORREGIDO: Actualizar todas las dimensiones
|
||||
Width = ancho;
|
||||
Height = altoGuia;
|
||||
GuideThickness = altoGuia; // Mantener compatibilidad
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO - Actualiza las dimensiones y recrea el cuerpo físico si es necesario
|
||||
/// </summary>
|
||||
public void SetDimensions(float width, float height)
|
||||
{
|
||||
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||
Width = width;
|
||||
Height = height;
|
||||
GuideThickness = height; // Mantener compatibilidad
|
||||
|
||||
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(width, GuideThickness, zAltura_Guia);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO - Actualiza posición usando las dimensiones reales almacenadas
|
||||
/// </summary>
|
||||
public void SetPosition(Vector2 wpfTopLeft, float wpfAngle, float actualWidth, float actualHeight)
|
||||
{
|
||||
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||
Width = actualWidth;
|
||||
Height = actualHeight;
|
||||
GuideThickness = actualHeight; // Mantener compatibilidad
|
||||
|
||||
// ✅ CRÍTICO: Recrear el box colisionador con las nuevas dimensiones
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(actualWidth, GuideThickness, zAltura_Guia);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada con dimensiones reales
|
||||
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, actualWidth, actualHeight, wpfAngle, zPosition);
|
||||
|
||||
// Actualizar posición y rotación simultáneamente
|
||||
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO: Actualiza tanto posición como rotación usando dimensiones reales
|
||||
/// </summary>
|
||||
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float width, float height)
|
||||
{
|
||||
// ✅ NUEVO: Actualizar dimensiones almacenadas
|
||||
Width = width;
|
||||
Height = height;
|
||||
GuideThickness = height; // Mantener compatibilidad
|
||||
|
||||
// ✅ CRÍTICO: Recrear el box colisionador con las nuevas dimensiones
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(width, GuideThickness, zAltura_Guia);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada con dimensiones reales
|
||||
var zPosition = zAltura_Guia / 2 + zPos_Guia;
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||
|
||||
// Actualizar posición y rotación simultáneamente
|
||||
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO - Usar dimensiones reales almacenadas en lugar de aproximaciones
|
||||
/// </summary>
|
||||
public void SetPosition(Vector2 wpfTopLeft, float wpfAngle = 0)
|
||||
{
|
||||
// ✅ CORREGIDO: Usar dimensiones reales almacenadas en lugar de aproximaciones
|
||||
SetPosition(wpfTopLeft, wpfAngle, Width, Height);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
using BepuPhysics.Collidables;
|
||||
using BepuPhysics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
||||
public class simTransporte : simBase
|
||||
{
|
||||
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
|
||||
public float Friction { get; set; } // Friccion para efectos de cinta transportadora
|
||||
public float DistanceGuide2Guide { get; set; }
|
||||
public bool isBrake { get; set; }
|
||||
|
||||
public bool TransportWithGuides = false;
|
||||
private List<Action> _deferredActions;
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
|
||||
// ✅ NUEVAS PROPIEDADES - cachear cálculos costosos
|
||||
public Vector3 DirectionVector { get; private set; }
|
||||
public List<simBotella> BottlesOnTransport { get; private set; } = new List<simBotella>();
|
||||
|
||||
// ✅ NUEVO EVENTO - para actualización de motores
|
||||
public event Action<simTransporte> OnSpeedChanged;
|
||||
|
||||
|
||||
|
||||
public simTransporte(Simulation simulation, List<Action> deferredActions, float width, float height, Vector2 topLeft, float angle = 0, SimulationManagerBEPU simulationManager = null)
|
||||
{
|
||||
_simulation = simulation;
|
||||
_deferredActions = deferredActions;
|
||||
_simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
// Usar el nuevo método Create que maneja Top-Left correctamente
|
||||
Create(width, height, topLeft, angle);
|
||||
|
||||
// ✅ INICIALIZAR PROPIEDADES CRÍTICAS
|
||||
UpdateCachedProperties();
|
||||
}
|
||||
|
||||
public float Angle
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetRotationZ();
|
||||
}
|
||||
set
|
||||
{
|
||||
SetRotation(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SOBRESCRITO: SetRotation que actualiza automáticamente las propiedades cacheadas
|
||||
/// </summary>
|
||||
public new void SetRotation(float wpfAngle)
|
||||
{
|
||||
base.SetRotation(wpfAngle);
|
||||
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de rotación
|
||||
UpdateCachedProperties();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public new void SetPosition(float x, float y, float z = 0)
|
||||
{
|
||||
base.SetPosition(x, y, z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza posición desde Top-Left WPF con dimensiones y ángulo actuales
|
||||
/// </summary>
|
||||
internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
|
||||
{
|
||||
var currentWpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
|
||||
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, currentWpfAngle, zPosition);
|
||||
SetPosition(bepuCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
|
||||
/// </summary>
|
||||
internal Vector2 GetWpfTopLeft()
|
||||
{
|
||||
var bepuCenter = GetPosition();
|
||||
var wpfAngle = GetRotationZ(); // Ya usa CoordinateConverter
|
||||
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(bepuCenter, Width, Height, wpfAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
|
||||
/// </summary>
|
||||
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle)
|
||||
{
|
||||
var zPosition = GetPosition().Z; // Mantener Z actual
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, Width, Height, wpfAngle, zPosition);
|
||||
CoordinateConverter.UpdateBepuBodyPose(_simulation, BodyHandle, bepuCenter, wpfAngle);
|
||||
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de orientación
|
||||
UpdateCachedProperties();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
// ✅ NUEVO MÉTODO - actualizar propiedades cacheadas
|
||||
internal void UpdateCachedProperties()
|
||||
{
|
||||
// ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte
|
||||
// NO depende de las dimensiones (Width >= Height) sino solo de la rotación
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
var bepuQuaternion = bodyReference.Pose.Orientation;
|
||||
|
||||
// ✅ SIEMPRE usar UnitX y aplicar la rotación
|
||||
DirectionVector = Vector3.Transform(Vector3.UnitX, bepuQuaternion);
|
||||
|
||||
// 🔍 DEBUG: Agregar información detallada
|
||||
var wpfAngle = GetRotationZ();
|
||||
//System.Diagnostics.Debug.WriteLine($"[UpdateCached] WPF Angle: {wpfAngle}°, DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ✅ CORREGIDO: Aplicar conversión de coordenadas WPF→BEPU en el vector
|
||||
var wpfAngle = GetRotationZ(); // Ángulo WPF en grados
|
||||
var wpfAngleRadians = simBase.GradosARadianes(wpfAngle);
|
||||
|
||||
// Calcular el vector en coordenadas WPF
|
||||
var wpfX = (float)Math.Cos(wpfAngleRadians);
|
||||
var wpfY = (float)Math.Sin(wpfAngleRadians);
|
||||
|
||||
// ✅ APLICAR CONVERSIÓN Y: En WPF Y+ es abajo, en BEPU Y+ es arriba
|
||||
DirectionVector = new Vector3(wpfX, -wpfY, 0); // Invertir Y para conversión WPF→BEPU
|
||||
|
||||
// 🔍 DEBUG: Agregar información detallada
|
||||
System.Diagnostics.Debug.WriteLine($"[UpdateCached-Fallback] WPF Angle: {wpfAngle}°, WPF Vector: ({wpfX:F3}, {wpfY:F3}), BEPU DirectionVector: {DirectionVector}, Length: {DirectionVector.Length()}");
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed;
|
||||
UpdateCachedProperties();
|
||||
// Disparar evento para actualizar motores activos
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configura la velocidad del transporte en metros por segundo
|
||||
/// Valores positivos mueven en la dirección del transporte, negativos en dirección opuesta
|
||||
/// </summary>
|
||||
/// <param name="speedMeterPerSecond">Velocidad en m/s (típicamente entre -5.0 y 5.0)</param>
|
||||
public void SetTransportSpeed(float speedMeterPerSecond)
|
||||
{
|
||||
SetSpeed(speedMeterPerSecond * simBase.SPEED_CONVERSION_FACTOR);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detiene completamente el transporte
|
||||
/// </summary>
|
||||
public void StopTransport()
|
||||
{
|
||||
SetSpeed(0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invierte la dirección del transporte manteniendo la misma velocidad
|
||||
/// </summary>
|
||||
public void ReverseTransport()
|
||||
{
|
||||
SetSpeed(-Speed);
|
||||
}
|
||||
|
||||
public void SetDimensions(float width, float height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
|
||||
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
|
||||
{
|
||||
var box = new Box(width, height, zAltura_Transporte);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de dimensiones
|
||||
UpdateCachedProperties();
|
||||
|
||||
// ✅ CRÍTICO: Disparar evento para actualizar motores activos con nueva dirección
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public void Create(float width, float height, Vector2 wpfTopLeft, float wpfAngle = 0)
|
||||
{
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var zPosition = zAltura_Transporte / 2f + zPos_Transporte;
|
||||
var bepuCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, width, height, wpfAngle, zPosition);
|
||||
|
||||
Create(width, height, bepuCenter, wpfAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SIMPLIFICADO: RemoverBody que limpia restricciones y elimina el body
|
||||
/// </summary>
|
||||
public new void RemoverBody()
|
||||
{
|
||||
// ✅ NUEVO: Limpiar restricciones de botellas conectadas antes de eliminar el cuerpo
|
||||
if (_simulationManager != null)
|
||||
{
|
||||
var connectedBottles = _simulationManager.GetBottlesConnectedToElement(this);
|
||||
foreach (var bottle in connectedBottles)
|
||||
{
|
||||
bottle.CleanRestrictions(this);
|
||||
}
|
||||
}
|
||||
|
||||
base.RemoverBody();
|
||||
}
|
||||
|
||||
public void Create(float width, float height, Vector3 bepuPosition, float wpfAngle = 0)
|
||||
{
|
||||
RemoverBody();
|
||||
|
||||
var box = new Box(width, height, zAltura_Transporte);
|
||||
var shapeIndex = _simulation.Shapes.Add(box);
|
||||
|
||||
// ✅ USAR COORDINATECONVERTER para conversión centralizada
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
|
||||
new CollidableDescription(shapeIndex, 0.1f),
|
||||
new BodyActivityDescription(0.01f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas después de crear el body
|
||||
UpdateCachedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue