Compare commits
6 Commits
3eee0e3d9b
...
d1ec7f4d12
Author | SHA1 | Date |
---|---|---|
|
d1ec7f4d12 | |
|
83fc828a4c | |
|
c91b1419b4 | |
|
71c08d8047 | |
|
eb6ed62d5b | |
|
de0e3ce5f0 |
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
Quisiera que con los conocimientos importantes y desiciones importantes que hemos adquirido y utilizado los agregues a Documentation\MemoriadeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.
|
|
@ -0,0 +1,32 @@
|
|||
### Aqui se mantiene la memoria de evolucion de las distintas decisiones que fueron tomadas y porque
|
||||
|
||||
BEPU.cs : SimulationManagerBEPU gestor de la simulacion con el motor BEPUphysics y punto de creacion y modificacion de los objetos dentro del mundo que se derivan des simBase
|
||||
|
||||
simBase : Clase base que da un marco a el resto de los objetos
|
||||
simBarrera : Simula una fotocelula con un espejo usando RayCast
|
||||
simBotella : Simula una botella que puede transitar por los transportes simTransporte o simCurve
|
||||
simCurve : Simula una curva o arco de curva de transporte
|
||||
simDescarte : Permite la eliminacion localizada de botellas en un punto del mundo
|
||||
simGuia : Es un box con el que las botellas pueden colisionar y cambiar de direccion.
|
||||
simTransporte : Es un box por donde las botellas pueden desplazarse usando un truco de aplicar la Velocity.Linear pero sin integrar esta velocidad para que no se mueva el objeto transporte.
|
||||
|
||||
* Se usaron esferas en vez de cilindros para mejorar la eficiencia. En el Debug3D si se usan cilindros. **Revertido**: Se ha vuelto a usar `Cylinder` para las `simBotella` ya que el nuevo sistema de fricción debería prevenir la rotación indeseada que ocurría con los `LinearAxisMotor`.
|
||||
|
||||
* Se reemplazó el sistema de `LinearAxisMotor` que actuaba sobre las `simBotella` por un sistema basado en fricción. Los transportes (`simTransporte` y `simCurve`) ahora son cuerpos cinemáticos que no se mueven de su sitio. Para lograr que arrastren a las botellas, se les asigna una velocidad (`Velocity.Linear` o `Velocity.Angular`) justo antes de que el solver se ejecute (en `OnSubstepStarted`) y se les quita justo después (en `OnSubstepEnded`). Esto permite que los cuerpos cinemáticos transmitan su velocidad a través de la fricción durante la simulación, pero evita que el `PoseIntegrator` los desplace de su posición original, ya que su velocidad es cero cuando se integra la pose.
|
||||
|
||||
* Para aumentar la estabilidad de las `simBotella` y evitar que roten descontroladamente sobre su eje Z al ser arrastradas, se implementó un callback `IntegrateVelocity` personalizado. Este callback identifica las botellas durante la integración y les aplica un amortiguamiento angular (`AngularDamping`) adicional solo en el eje Z. Se descartó la idea inicial de modificar dinámicamente la masa de las botellas dentro de este callback, ya que la arquitectura de BEPUphysics no permite cambiar la masa o la inercia de un cuerpo durante la fase de integración de velocidad.
|
||||
|
||||
* Originalmente se habia puesto todos los objetos en awaken para poder usar las colisiones constantemente incluso con objetos en modo sleep para que los simBarrera puedan detectar las colisiones. Ahora que se usar RayCast podemos dejar que las simBotellas se duerman
|
||||
|
||||
* La unica clase que se ha terminado de refactorizar respecto a el cambio de coordenadas es simBarrera y ucPhotocell. El concepto es poder separar usando metodos de SimulationManagerBEPU y estructuras como BarreraData la conversion de coordenadas de WPF a coordenadas BEPU. Para esto se usa CoordinateConverter que permite bidireccionalmente convertir las coordenadas pero esto solo se debe usar en SimulationManagerBEPU las clases derivadas de osBase solo deben manejar coordenadas WPF, mientras que las clases dervadas de simBase solo deben almacenar y usar coordenadas BEPU. La z tambien es algo que se debe transferir a SimulationManagerBEPU ya que los objetos simBase deberian recibir tambien sus coordenadas de Z desde SimulationManagerBEPU y ser SimulationManagerBEPU el que gestione las Z.
|
||||
|
||||
* Se ha implementado un sistema para evitar que las botellas (`simBotella`) "floten" o se eleven de manera irreal por la acumulación de presión en la simulación. Cada botella ahora registra si está en contacto con un transporte y almacena la última coordenada Z válida durante dicho contacto. Si la botella deja de tener contacto con transportes por varios frames consecutivos, se incrementa un contador de "presión". Al superar un umbral, el sistema reestablece la posición Z de la botella a su última altura conocida, previniendo la flotación. Este contador de presión se decrementa rápidamente al volver a hacer contacto con un transporte.
|
||||
|
||||
* Cuando una botella (`simBotella`) entra en contacto con un transporte de frenado (`simTransporte` con `isBrake = true`), su posición se ajusta automáticamente para centrarla en el eje longitudinal del transporte. Esto se realiza una única vez, en el primer contacto, para asegurar un acoplamiento suave y predecible. La posición de la botella se proyecta sobre la línea central del transporte y su velocidad lateral se anula, evitando que la botella se desvíe mientras frena y se alinea con el flujo de salida.
|
||||
|
||||
* Se ha implementado un indicador visual simple para mostrar cuando los transportes (`simTransporte`) están en movimiento. El sistema detecta automáticamente si un transporte tiene velocidad (`Speed > 0.001`) y cambia su color a verde brillante. Los transportes detenidos se muestran en verde normal. Esta funcionalidad se integra en el sistema de materiales del `BEPUVisualization3DManager` y se actualiza automáticamente cuando cambia el estado del transporte, proporcionando una retroalimentación visual inmediata del estado de operación.
|
||||
|
||||
* Se ha implementado un sistema de animaciones automáticas usando StoryBoard de WPF para los transportes en movimiento. Los transportes activos muestran una animación continua que combina: (1) rotación sutil muy lenta alrededor del eje Z (20 segundos por vuelta completa) y (2) pulsación cíclica del color del material (1.5 segundos por ciclo). Las animaciones se crean y destruyen automáticamente según el estado del transporte, sin necesidad de actualización manual en cada frame. El sistema gestiona las animaciones activas en un diccionario y las limpia correctamente cuando se eliminan objetos. Se resolvió el problema de `InvalidOperationException` al animar brushes inmutables creando una función `CreateAnimatableMaterial` que genera materiales específicamente diseñados para ser animados sin estar "frozen", proporcionando una experiencia visual fluida y eficiente.
|
||||
|
||||
* Se ha mejorado el sistema de guías curvas (`ucTransporteCurvaGuias`) para incluir apertura en cono en los extremos de entrada y salida. Se agregó el parámetro `AnguloAperturaGuias` (por defecto 5 grados) que permite configurar la apertura modificando los radios de las guías en los puntos extremos. En lugar de cambiar ángulos, se reduce el radio de la guía superior (externa) y se aumenta el radio de la guía inferior (interna) en los segmentos inicial y final, creando naturalmente la apertura en cono. La modificación del radio se calcula usando `Math.Sin(anguloApertura)` para obtener el desplazamiento apropiado. Esta apertura facilita la entrada y salida de botellas del transporte curvo, reduciendo atascos y mejorando el flujo de materiales manteniendo la continuidad geométrica de las guías.
|
||||
|
|
@ -146,7 +146,9 @@ namespace CtrEditor.ObjetosSim
|
|||
// Se llama cuando inicia la simulación - crear geometría si no existe
|
||||
if (SimGeometria == null)
|
||||
{
|
||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
||||
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||
SimGeometria.SimObjectType = "Botella";
|
||||
SimGeometria.WpfObject = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -170,14 +172,12 @@ namespace CtrEditor.ObjetosSim
|
|||
SetCentro(SimGeometria.Center);
|
||||
|
||||
// Sistema de colores jerarquizado para diferentes estados
|
||||
if (SimGeometria.isRestricted)
|
||||
ColorButton_oculto = Brushes.Yellow; // Estado restringido (prioridad alta)
|
||||
else if (SimGeometria.isOnBrakeTransport)
|
||||
ColorButton_oculto = Brushes.Blue; // En transporte con freno - NUEVO ESTADO
|
||||
else if (SimGeometria.IsOnAnyTransport())
|
||||
ColorButton_oculto = Brushes.Red; // En transporte normal
|
||||
if (SimGeometria.isOnBrakeTransport)
|
||||
ColorButton_oculto = Brushes.Blue; // En transporte con freno (prioridad sobre presión)
|
||||
else if (!SimGeometria.isOnBrakeTransport && SimGeometria.PressureBuildup>1)
|
||||
ColorButton_oculto = Brushes.Red; // La botella tiene mucha presion
|
||||
else
|
||||
ColorButton_oculto = Brushes.Gray; // Estado libre
|
||||
ColorButton_oculto = Brushes.Gray; // 5. Estado libre
|
||||
|
||||
// Ha sido marcada para remover
|
||||
if (SimGeometria.Descartar)
|
||||
|
@ -196,7 +196,7 @@ namespace CtrEditor.ObjetosSim
|
|||
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
||||
// crear el objeto de simulacion
|
||||
base.ucLoaded();
|
||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
||||
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||
}
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
|
|
|
@ -180,7 +180,7 @@ namespace CtrEditor.ObjetosSim
|
|||
// El UserControl ya se ha cargado y podemos obtener las coordenadas para
|
||||
// crear el objeto de simulacion
|
||||
base.ucLoaded();
|
||||
SimGeometria = simulationManager.AddCircle(Diametro, GetCentro(), Mass);
|
||||
SimGeometria = simulationManager.AddBotella(Diametro, GetCentro(), Mass);
|
||||
}
|
||||
public override void ucUnLoaded()
|
||||
{
|
||||
|
|
|
@ -87,6 +87,7 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Category("Información")]
|
||||
[property: Name("Salida Filtro")]
|
||||
bool filter_Output;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Cantidad de botellas generadas por hora")]
|
||||
[property: Category("Configuración")]
|
||||
|
@ -113,11 +114,32 @@ namespace CtrEditor.ObjetosSim
|
|||
[property: Name("Velocidad Actual (%)")]
|
||||
private float velocidad_actual_percentual;
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Distancia libre minima sin botellas sobre el transporte para colocar otra botella")]
|
||||
[property: Category("Simulación")]
|
||||
[property: Name("Diametro libre minimo")]
|
||||
private float distancia_libre;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Description("Diámetro de las botellas generadas")]
|
||||
[property: Category("Configuración")]
|
||||
[property: Name("Diámetro Botella")]
|
||||
private float diametro_botella;
|
||||
|
||||
public osBottGenerator()
|
||||
{
|
||||
Ancho = 0.40f;
|
||||
Alto = 0.40f;
|
||||
Angulo = 0;
|
||||
Velocidad_actual_percentual = 100;
|
||||
Diametro_botella = 0.1f;
|
||||
Botellas_hora = 10000;
|
||||
Consenso = true;
|
||||
Distancia_libre = 0.1f;
|
||||
}
|
||||
|
||||
|
||||
public override void UpdatePLC(PLCViewModel plc, int elapsedMilliseconds)
|
||||
{
|
||||
if (Consenso_NC)
|
||||
|
@ -128,7 +150,7 @@ namespace CtrEditor.ObjetosSim
|
|||
|
||||
private bool HayEspacioParaNuevaBotella(float X, float Y)
|
||||
{
|
||||
float radioMinimo = Diametro_botella / 4; // Distancia mínima entre centros
|
||||
float radioMinimo = Distancia_libre; // Distancia mínima entre centros
|
||||
float radioMinimoCuadrado = radioMinimo * radioMinimo;
|
||||
|
||||
// Buscar todas las botellas cercanas
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace CtrEditor.ObjetosSim
|
|||
// ✅ SISTEMA INTELIGENTE: Solo recrear si las dimensiones han cambiado
|
||||
if (HasGuideDimensionsChanged(SimGeometria, Ancho, AltoGuia))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[osGuia] Recreando guía por cambio de dimensiones: {Ancho}x{AltoGuia}");
|
||||
//System.Diagnostics.Debug.WriteLine($"[osGuia] Recreando guía por cambio de dimensiones: {Ancho}x{AltoGuia}");
|
||||
|
||||
// ✅ RECREAR COMPLETAMENTE: Las dimensiones cambiaron
|
||||
SimGeometria.Create(Ancho, AltoGuia, topLeft, Angulo);
|
||||
|
@ -56,7 +56,7 @@ namespace CtrEditor.ObjetosSim
|
|||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[osGuia] Solo actualizando posición/rotación: Left={Left}, Top={Top}, Angulo={Angulo}");
|
||||
// System.Diagnostics.Debug.WriteLine($"[osGuia] Solo actualizando posición/rotación: Left={Left}, Top={Top}, Angulo={Angulo}");
|
||||
|
||||
// ✅ SOLO ACTUALIZAR POSICIÓN/ROTACIÓN: Usar dimensiones reales para conversión correcta
|
||||
SimGeometria.UpdateFromWpfParameters(topLeft, Angulo, Ancho, AltoGuia);
|
||||
|
|
|
@ -134,6 +134,17 @@ namespace CtrEditor.ObjetosSim
|
|||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Ángulo de apertura en grados para los extremos de las guías")]
|
||||
[property: Name("Ángulo Apertura Guías")]
|
||||
private float anguloAperturaGuias = 5f;
|
||||
|
||||
partial void OnAnguloAperturaGuiasChanged(float value)
|
||||
{
|
||||
ActualizarGeometrias();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[property: Category("Configuración")]
|
||||
[property: Description("Distancia de separación de las guías desde el borde")]
|
||||
|
@ -336,6 +347,7 @@ namespace CtrEditor.ObjetosSim
|
|||
float anguloInicioRad = simBase.GradosARadianes(Angulo);
|
||||
float anguloFinalRad = simBase.GradosARadianes(AnguloFinal);
|
||||
float rangoAngular = anguloFinalRad - anguloInicioRad;
|
||||
float anguloAperturaRad = simBase.GradosARadianes(AnguloAperturaGuias);
|
||||
|
||||
// Calcular el paso angular entre segmentos
|
||||
float pasoAngular = rangoAngular / NumeroSegmentosGuias;
|
||||
|
@ -349,14 +361,27 @@ namespace CtrEditor.ObjetosSim
|
|||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||
|
||||
// Calcular radios ajustados para crear apertura en cono
|
||||
// Para guía superior: reducir radio en los extremos para crear apertura
|
||||
float radio1 = radioGuiaSuperior;
|
||||
float radio2 = radioGuiaSuperior;
|
||||
|
||||
// Calcular reducción del radio basada en el ángulo de apertura
|
||||
float reduccionRadio = radioGuiaSuperior * (float)Math.Sin(anguloAperturaRad/10);
|
||||
|
||||
if (InvertirDireccion && i == 0) // Primer segmento
|
||||
radio1 += reduccionRadio;
|
||||
if (!InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
|
||||
radio2 += reduccionRadio;
|
||||
|
||||
Vector2 punto1 = new Vector2(
|
||||
radioGuiaSuperior * (float)Math.Cos(angulo1),
|
||||
radioGuiaSuperior * (float)Math.Sin(angulo1)
|
||||
radio1 * (float)Math.Cos(angulo1),
|
||||
radio1 * (float)Math.Sin(angulo1)
|
||||
);
|
||||
|
||||
Vector2 punto2 = new Vector2(
|
||||
radioGuiaSuperior * (float)Math.Cos(angulo2),
|
||||
radioGuiaSuperior * (float)Math.Sin(angulo2)
|
||||
radio2 * (float)Math.Cos(angulo2),
|
||||
radio2 * (float)Math.Sin(angulo2)
|
||||
);
|
||||
|
||||
// Ajustar por la posición del objeto
|
||||
|
@ -374,14 +399,27 @@ namespace CtrEditor.ObjetosSim
|
|||
float angulo1 = anguloInicioRad + i * pasoAngular;
|
||||
float angulo2 = anguloInicioRad + (i + 1) * pasoAngular;
|
||||
|
||||
// Calcular radios ajustados para crear apertura en cono
|
||||
// Para guía inferior: aumentar radio en los extremos para crear apertura
|
||||
float radio1 = radioGuiaInferior;
|
||||
float radio2 = radioGuiaInferior;
|
||||
|
||||
// Calcular aumento del radio basada en el ángulo de apertura
|
||||
float aumentoRadio = radioGuiaInferior * (float)Math.Sin(anguloAperturaRad/10);
|
||||
|
||||
if (InvertirDireccion && i == 0) // Primer segmento
|
||||
radio1 -= aumentoRadio;
|
||||
if (InvertirDireccion && i == NumeroSegmentosGuias - 1) // Último segmento
|
||||
radio2 -= aumentoRadio;
|
||||
|
||||
Vector2 punto1 = new Vector2(
|
||||
radioGuiaInferior * (float)Math.Cos(angulo1),
|
||||
radioGuiaInferior * (float)Math.Sin(angulo1)
|
||||
radio1 * (float)Math.Cos(angulo1),
|
||||
radio1 * (float)Math.Sin(angulo1)
|
||||
);
|
||||
|
||||
Vector2 punto2 = new Vector2(
|
||||
radioGuiaInferior * (float)Math.Cos(angulo2),
|
||||
radioGuiaInferior * (float)Math.Sin(angulo2)
|
||||
radio2 * (float)Math.Cos(angulo2),
|
||||
radio2 * (float)Math.Sin(angulo2)
|
||||
);
|
||||
|
||||
// Ajustar por la posición del objeto
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace CtrEditor.Simulacion
|
|||
public struct NarrowPhaseCallbacks : INarrowPhaseCallbacks
|
||||
{
|
||||
private SimulationManagerBEPU _simulationManager;
|
||||
private const float minPressureToLostFriction = 1;
|
||||
|
||||
public NarrowPhaseCallbacks(SimulationManagerBEPU simulationManager)
|
||||
{
|
||||
|
@ -98,26 +99,18 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
var botellaA = GetBotellaFromCollidable(pair.A);
|
||||
var botellaB = GetBotellaFromCollidable(pair.B);
|
||||
// ✅ ELIMINADO: Las barreras ya no tienen body físico
|
||||
// var barreraA = GetBarreraFromCollidable(pair.A);
|
||||
// var barreraB = GetBarreraFromCollidable(pair.B);
|
||||
var descarteA = GetDescarteFromCollidable(pair.A);
|
||||
var descarteB = GetDescarteFromCollidable(pair.B);
|
||||
var botella = botellaA ?? botellaB;
|
||||
|
||||
// ✅ ELIMINADO: Las barreras ya no participan en colisiones físicas
|
||||
// if (barreraA != null || barreraB != null)
|
||||
// {
|
||||
// return false; // NO generar contacto físico
|
||||
// }
|
||||
|
||||
// ✅ DESCARTES como sensores puros
|
||||
if (descarteA != null || descarteB != null)
|
||||
{
|
||||
var descarte = descarteA ?? descarteB;
|
||||
if (botella != null)
|
||||
{
|
||||
_simulationManager.RegisterDescarteContact(descarte, botella);
|
||||
// ✅ CORREGIDO: Marcar la botella para eliminar directamente, en lugar de registrar el contacto.
|
||||
// Esto es más directo y evita el error de compilación.
|
||||
botella.Descartar = true;
|
||||
}
|
||||
return false; // NO generar contacto físico
|
||||
}
|
||||
|
@ -128,50 +121,148 @@ namespace CtrEditor.Simulacion
|
|||
var curveA = GetCurveFromCollidable(pair.A);
|
||||
var curveB = GetCurveFromCollidable(pair.B);
|
||||
|
||||
// ✅ CONTACTO BOTELLA-TRANSPORTE: Crear o actualizar motor inmediatamente
|
||||
// ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
||||
if (botella != null && (transportA != null || transportB != null))
|
||||
{
|
||||
// La velocidad del cuerpo cinemático del transporte se establece directamente
|
||||
// en la clase simTransporte. El motor de físicas se encarga del resto.
|
||||
|
||||
// ✅ NUEVO: Lógica de control de presión/flotación
|
||||
botella.IsTouchingTransport = true;
|
||||
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
|
||||
if (botellaCollidable.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botellaCollidable.BodyHandle))
|
||||
{
|
||||
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
||||
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
||||
}
|
||||
// Decrementamos la presión acumulada al tocar un transporte. Se reduce más rápido de lo que aumenta.
|
||||
botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f);
|
||||
|
||||
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
|
||||
var transport = transportA ?? transportB;
|
||||
if (transport.isBrake)
|
||||
{
|
||||
botella.PressureBuildup = 0;
|
||||
// ✅ NUEVO: Centrar la botella en el transporte de frenado la primera vez que entra.
|
||||
// if (!botella.isOnBrakeTransport)
|
||||
{
|
||||
if (botella.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botella.BodyHandle) &&
|
||||
transport.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(transport.BodyHandle))
|
||||
{
|
||||
var bottleBody = _simulationManager.simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
||||
var transportBody = _simulationManager.simulation.Bodies.GetBodyReference(transport.BodyHandle);
|
||||
|
||||
// ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE
|
||||
transport.UpdateCachedProperties();
|
||||
var direction = transport.DirectionVector;
|
||||
var speed = transport.Speed;
|
||||
botella.CreateOrUpdateMotor(transport, direction, speed);
|
||||
var bottlePose = bottleBody.Pose;
|
||||
var transportPose = transportBody.Pose;
|
||||
|
||||
// Proyectar la posición de la botella sobre el eje longitudinal del transporte.
|
||||
Vector3 transportForward = transport.DirectionVector;
|
||||
Vector3 vectorToBottle = bottlePose.Position - transportPose.Position;
|
||||
float projectionLength = Vector3.Dot(vectorToBottle, transportForward);
|
||||
Vector3 newPositionOnCenterline = transportPose.Position + transportForward * projectionLength;
|
||||
|
||||
// Crear la nueva pose manteniendo la Z original de la botella para no hundirla.
|
||||
var newBottlePosition = new Vector3(newPositionOnCenterline.X, newPositionOnCenterline.Y, bottlePose.Position.Z);
|
||||
bottleBody.Pose.Position = newBottlePosition;
|
||||
|
||||
// Opcional: Anular la velocidad lateral para un acoplamiento más suave.
|
||||
var lateralVelocity = Vector3.Dot(bottleBody.Velocity.Linear, transport.DirectionVector);
|
||||
bottleBody.Velocity.Linear = transport.DirectionVector * lateralVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
botella.isOnBrakeTransport = true;
|
||||
pairMaterial.FrictionCoefficient = 14f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (botella.PressureBuildup > minPressureToLostFriction)
|
||||
{
|
||||
botella.isOnBrakeTransport = false;
|
||||
pairMaterial.FrictionCoefficient = 0.1f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
botella.isOnBrakeTransport = false;
|
||||
pairMaterial.FrictionCoefficient = 1.2f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//Fricción alta para transportes
|
||||
pairMaterial.FrictionCoefficient = 0.01f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 6);
|
||||
}
|
||||
// ✅ CONTACTO BOTELLA-CURVA: Crear o actualizar motor inmediatamente
|
||||
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
|
||||
else if (botella != null && (curveA != null || curveB != null))
|
||||
{
|
||||
var curve = curveA ?? curveB;
|
||||
// El motor de físicas usará la velocidad angular del cuerpo cinemático de la curva
|
||||
// para calcular las fuerzas de fricción y arrastrar la botella.
|
||||
|
||||
// ✅ CREAR O ACTUALIZAR MOTOR DINÁMICO INMEDIATAMENTE
|
||||
var direction = _simulationManager.CalculateCurveDirectionFromBottlePosition(curve, botella);
|
||||
botella.CreateOrUpdateMotor(curve, direction, curve.Speed);
|
||||
// ✅ NUEVO: Lógica de control de presión/flotación
|
||||
botella.IsTouchingTransport = true;
|
||||
botella.isOnBrakeTransport = false;
|
||||
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
|
||||
if (botellaCollidable.BodyHandle.Value >= 0 && _simulationManager.simulation.Bodies.BodyExists(botellaCollidable.BodyHandle))
|
||||
{
|
||||
var body = _simulationManager.simulation.Bodies.GetBodyReference(botellaCollidable.BodyHandle);
|
||||
botella.LastTransportCollisionZ = body.Pose.Position.Z;
|
||||
}
|
||||
// Decrementamos la presión acumulada al tocar una curva.
|
||||
botella.PressureBuildup = Math.Max(0, botella.PressureBuildup - 0.1f);
|
||||
|
||||
// Fricción alta para curvas
|
||||
pairMaterial.FrictionCoefficient = 0.01f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 6);
|
||||
// ✅ Fricción ajustada para un arrastre firme pero no excesivo.
|
||||
if (botella.PressureBuildup > minPressureToLostFriction)
|
||||
{
|
||||
pairMaterial.FrictionCoefficient = 0.1f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
||||
}else
|
||||
{
|
||||
pairMaterial.FrictionCoefficient = 1f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.0f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
||||
}
|
||||
}
|
||||
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
|
||||
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
|
||||
{
|
||||
// Configuración específica para guías usando propiedades configurables
|
||||
// Fricción baja para las guías, deben deslizar, no arrastrar.
|
||||
pairMaterial.FrictionCoefficient = 0.01f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 6);
|
||||
pairMaterial.MaximumRecoveryVelocity = 2f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(20, 1);
|
||||
}
|
||||
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Configuración más suave para evitar el comportamiento "pegajoso".
|
||||
else if (botellaA != null && botellaB != null)
|
||||
{
|
||||
if (botella.isOnBrakeTransport)
|
||||
{
|
||||
pairMaterial.FrictionCoefficient = 2.0f; // Un poco menos de fricción.
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote.
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1f); // Muelle MUCHO más suave y críticamente amortiguado.
|
||||
}
|
||||
else
|
||||
{
|
||||
pairMaterial.FrictionCoefficient = 0.01f; // Un poco menos de fricción.
|
||||
pairMaterial.MaximumRecoveryVelocity = 1.5f; // Menos rebote.
|
||||
pairMaterial.SpringSettings = new SpringSettings(20, 0.5f); // Muelle MUCHO más suave y críticamente amortiguado.
|
||||
}
|
||||
// Si una botella tiene presion esta presion es transmitia a el resto para que el resto pierda el coeficiente de friccion sobre los transportes
|
||||
if (botellaA.PressureBuildup > 0 && botellaB.PressureBuildup == 0)
|
||||
botellaB.PressureBuildup = botellaA.PressureBuildup;
|
||||
if (botellaB.PressureBuildup > 0 && botellaA.PressureBuildup == 0)
|
||||
botellaA.PressureBuildup = botellaB.PressureBuildup;
|
||||
|
||||
}
|
||||
// Ajustes básicos para otras botellas
|
||||
else if (botella != null)
|
||||
{
|
||||
pairMaterial.FrictionCoefficient = 0.01f;
|
||||
// Fricción moderada para colisiones entre botellas.
|
||||
pairMaterial.FrictionCoefficient = 0.3f;
|
||||
pairMaterial.MaximumRecoveryVelocity = 1f;
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 6);
|
||||
pairMaterial.SpringSettings = new SpringSettings(80, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,30 +396,54 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
}
|
||||
|
||||
// ✅ REESCRITO COMPLETAMENTE: para corregir errores de compilación y aplicar la lógica de amortiguamiento de forma segura.
|
||||
public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
|
||||
{
|
||||
// Aplicar gravedad
|
||||
// 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
|
||||
var gravityWide = Vector3Wide.Broadcast(Gravity);
|
||||
velocity.Linear += gravityWide * dt;
|
||||
velocity.Linear.X += gravityWide.X * dt;
|
||||
velocity.Linear.Y += gravityWide.Y * dt;
|
||||
velocity.Linear.Z += gravityWide.Z * dt;
|
||||
|
||||
// ✅ ELIMINADO COMPLETAMENTE - ya no se necesita lógica especial de frenado
|
||||
// El sistema LinearAxisMotor maneja automáticamente todas las fuerzas
|
||||
var linearDampingFactor = new Vector<float>(MathF.Pow(MathHelper.Clamp(1 - LinearDamping, 0, 1), dt[0]));
|
||||
var angularDampingFactor = new Vector<float>(MathF.Pow(MathHelper.Clamp(1 - AngularDamping, 0, 1), dt[0]));
|
||||
|
||||
// Aplicar amortiguamiento lineal y angular para simular resistencia del aire
|
||||
// Esto es crucial para que los cilindros se detengan de forma realista
|
||||
var linearDampingWide = Vector<float>.One * LinearDamping;
|
||||
var angularDampingWide = Vector<float>.One * AngularDamping;
|
||||
velocity.Linear *= linearDampingFactor;
|
||||
velocity.Angular *= angularDampingFactor;
|
||||
|
||||
velocity.Linear *= linearDampingWide;
|
||||
velocity.Angular *= angularDampingWide; // ¡Esto es clave para detener rotaciones infinitas!
|
||||
// 2. LÓGICA PERSONALIZADA PARA BOTELLAS (ESCALAR)
|
||||
const float bottleExtraZAngularDamping = 0.8f;
|
||||
var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]);
|
||||
|
||||
// Recorremos cada "carril" del lote de simulación.
|
||||
for (int i = 0; i < Vector<int>.Count; ++i)
|
||||
{
|
||||
if (integrationMask[i] != 0)
|
||||
{
|
||||
var bodyIndex = bodyIndices[i];
|
||||
// Asegurarse de que el índice del cuerpo es válido para el conjunto activo.
|
||||
if (bodyIndex < _simulationManager.simulation.Bodies.ActiveSet.Count)
|
||||
{
|
||||
var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex];
|
||||
if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella)
|
||||
{
|
||||
// B. DAMPING ANGULAR EXTRA EN EJE Z
|
||||
// Para modificar un solo carril de un Vector<T>, la forma más segura es extraer
|
||||
// los valores a un array, modificar el índice y crear un nuevo Vector<T>.
|
||||
var zVel = new float[Vector<float>.Count];
|
||||
velocity.Angular.Z.CopyTo(zVel, 0);
|
||||
zVel[i] *= bottleZAngularDampingFactor;
|
||||
velocity.Angular.Z = new Vector<float>(zVel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class SimulationManagerBEPU
|
||||
public class SimulationManagerBEPU : IDisposable
|
||||
{
|
||||
public Simulation simulation;
|
||||
public List<simBase> Cuerpos;
|
||||
|
@ -368,6 +483,10 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
private object _contactsLock = new object();
|
||||
|
||||
// ✅ NUEVO: Diccionario para mapear BodyHandle a simBase para acceso O(1).
|
||||
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
|
||||
public float GlobalTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene el objeto simBase correspondiente a un BodyHandle
|
||||
/// </summary>
|
||||
|
@ -386,37 +505,8 @@ namespace CtrEditor.Simulacion
|
|||
return simBase as simCurve;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Obtiene todas las botellas que están conectadas a un elemento específico
|
||||
/// </summary>
|
||||
/// <param name="element">Elemento (simTransporte o simCurve) del cual obtener las botellas conectadas</param>
|
||||
/// <returns>Lista de botellas conectadas al elemento</returns>
|
||||
public List<simBotella> GetBottlesConnectedToElement(simBase element)
|
||||
{
|
||||
var connectedBottles = new List<simBotella>();
|
||||
|
||||
try
|
||||
{
|
||||
if (element == null) return connectedBottles;
|
||||
|
||||
// ✅ BUSCAR BOTELLAS QUE TENGAN ESTE ELEMENTO COMO TARGET
|
||||
foreach (var bottle in Cuerpos.OfType<simBotella>())
|
||||
{
|
||||
if (bottle != null && bottle.HasMotor && bottle.CurrentMotorTarget == element)
|
||||
{
|
||||
connectedBottles.Add(bottle);
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[GetBottlesConnectedToElement] Encontradas {connectedBottles.Count} botellas conectadas a {element.GetType().Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[GetBottlesConnectedToElement] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
|
||||
return connectedBottles;
|
||||
}
|
||||
// ✅ ELIMINADO: GetBottlesConnectedToElement ya no es necesario.
|
||||
// La conexión ahora es implícita a través de la física de contacto.
|
||||
|
||||
// ✅ NUEVOS - gestión automática de clasificación
|
||||
private void RegisterObjectHandle(simBase obj)
|
||||
|
@ -543,7 +633,55 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
simulation = Simulation.Create(bufferPool, narrowPhaseCallbacks, poseIntegratorCallbacks, solveDescription);
|
||||
|
||||
// ✅ NUEVO: Suscribirse a los eventos del solver para controlar la velocidad de los cuerpos cinemáticos.
|
||||
simulation.Solver.SubstepStarted += OnSubstepStarted;
|
||||
simulation.Solver.SubstepEnded += OnSubstepEnded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Se ejecuta al inicio de cada sub-paso de la simulación.
|
||||
/// Restaura las velocidades de los cuerpos cinemáticos para que el solver pueda usarlas para calcular la fricción.
|
||||
/// </summary>
|
||||
private void OnSubstepStarted(int substepIndex)
|
||||
{
|
||||
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
||||
{
|
||||
transport.ActualizarVelocidadCinematica();
|
||||
}
|
||||
foreach (var curve in Cuerpos.OfType<simCurve>())
|
||||
{
|
||||
curve.ActualizarVelocidadCinematica();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Se ejecuta al final de cada sub-paso de la simulación, pero antes de la integración de la pose.
|
||||
/// Pone a cero las velocidades de los cuerpos cinemáticos para evitar que el PoseIntegrator mueva su posición.
|
||||
/// </summary>
|
||||
private void OnSubstepEnded(int substepIndex)
|
||||
{
|
||||
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
||||
{
|
||||
if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle))
|
||||
{
|
||||
var body = simulation.Bodies.GetBodyReference(transport.BodyHandle);
|
||||
if (body.Kinematic)
|
||||
{
|
||||
body.Velocity.Linear = Vector3.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var curve in Cuerpos.OfType<simCurve>())
|
||||
{
|
||||
if (curve.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(curve.BodyHandle))
|
||||
{
|
||||
var body = simulation.Bodies.GetBodyReference(curve.BodyHandle);
|
||||
if (body.Kinematic)
|
||||
{
|
||||
body.Velocity.Angular = Vector3.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
|
@ -614,6 +752,10 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
// Limpiar visualización 3D
|
||||
Visualization3DManager?.Clear();
|
||||
|
||||
// ✅ NUEVO: Limpiar también el nuevo diccionario.
|
||||
CollidableData.Clear();
|
||||
GlobalTime = 0f;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -625,7 +767,7 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
try
|
||||
{
|
||||
// ✅ INICIALIZAR PROPIEDADES CACHEADAS
|
||||
// ✅ INICIALIZAR PROPIEDADES CACHEADAS Y VELOCIDADES CINEMÁTICAS
|
||||
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
||||
{
|
||||
transport.UpdateCachedProperties();
|
||||
|
@ -633,8 +775,7 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
foreach (var curve in Cuerpos.OfType<simCurve>())
|
||||
{
|
||||
// ✅ CORREGIDO: Usar método público para actualizar velocidad
|
||||
curve.SetSpeed(curve.Speed); // ✅ SIMPLIFICADO: Reinicializar velocidad
|
||||
curve.SetSpeed(curve.Speed);
|
||||
}
|
||||
|
||||
stopwatch.Start();
|
||||
|
@ -654,6 +795,12 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
_frameCount++;
|
||||
|
||||
// ✅ NUEVO: Resetear el estado de contacto para el sistema de presión
|
||||
foreach (var botella in Cuerpos.OfType<simBotella>())
|
||||
{
|
||||
botella.IsTouchingTransport = false;
|
||||
}
|
||||
|
||||
// ✅ CONSERVAR - validación de deltaTime
|
||||
var currentTime = stopwatch.Elapsed.TotalMilliseconds;
|
||||
var deltaTime = (float)((currentTime - stopwatch_last) / 1000.0);
|
||||
|
@ -717,8 +864,8 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
if (simulation.Bodies.BodyExists(cuerpo.BodyHandle))
|
||||
{
|
||||
cuerpo.LimitRotationToXYPlane();
|
||||
simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
|
||||
// cuerpo.LimitRotationToXYPlane();
|
||||
// simulation.Awakener.AwakenBody(cuerpo.BodyHandle);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -727,16 +874,10 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Detener motores de botellas que no están en contacto con elementos
|
||||
// Esto se ejecuta cada 10 frames para eficiencia
|
||||
if (_frameCount % 10 == 0)
|
||||
{
|
||||
//StopMotorsForBottlesNotInContact();
|
||||
}
|
||||
|
||||
// ✅ CONSERVAR - sistemas que funcionan bien
|
||||
ProcessBarreraContacts();
|
||||
ProcessDescarteContacts();
|
||||
ProcessPressureSystem();
|
||||
ProcessCleanupSystem();
|
||||
|
||||
// ✅ SIMPLIFICAR - limpiar solo contactos que usamos
|
||||
|
@ -763,53 +904,83 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Sistema para controlar la "presión" y evitar que las botellas floten
|
||||
/// Si una botella no toca un transporte por varios frames, se reestablece su altura.
|
||||
/// </summary>
|
||||
private void ProcessPressureSystem()
|
||||
{
|
||||
try
|
||||
{
|
||||
var botellas = Cuerpos.OfType<simBotella>().ToList();
|
||||
|
||||
foreach (var botella in botellas)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (botella == null || !simulation.Bodies.BodyExists(botella.BodyHandle))
|
||||
continue;
|
||||
|
||||
if (!botella.IsTouchingTransport && botella.LastTransportCollisionZ.HasValue)
|
||||
{
|
||||
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
|
||||
var currentZ = body.Pose.Position.Z;
|
||||
|
||||
// ✅ NUEVA CONDICIÓN: Solo actuar si la botella ha subido
|
||||
if (currentZ > botella.LastTransportCollisionZ.Value)
|
||||
{
|
||||
// Incrementar el contador de presión.
|
||||
botella.PressureBuildup++;
|
||||
|
||||
// Si se acumula suficiente presión (más de 2 frames sin contacto y subiendo),
|
||||
// es un indicio de que está flotando. La reestablecemos a su última Z conocida.
|
||||
if (botella.PressureBuildup > 2)
|
||||
{
|
||||
var pose = body.Pose;
|
||||
pose.Position.Z = botella.LastTransportCollisionZ.Value;
|
||||
body.Pose = pose;
|
||||
|
||||
// Opcional: resetear el contador para no aplicarlo constantemente en cada frame
|
||||
// botella.PressureBuildup = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ProcessPressureSystem] Error processing bottle {botella?.BodyHandle}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ProcessPressureSystem] Critical error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Marca un objeto para eliminación diferida (más seguro)
|
||||
/// </summary>
|
||||
public void Remove(simBase Objeto)
|
||||
{
|
||||
if (Objeto == null) return;
|
||||
|
||||
try
|
||||
if (Objeto != null)
|
||||
{
|
||||
// ✅ SIMPLIFICADO - eliminar lógica de masa especial
|
||||
UnregisterObjectHandle(Objeto); // ✅ NUEVO
|
||||
Objeto.RemoverBody(); // ✅ CONSERVAR - remover el cuerpo de BEPU
|
||||
|
||||
// ✅ NUEVO - Limpiar dimensiones almacenadas en osBase
|
||||
CtrEditor.ObjetosSim.osBase.ClearStoredDimensions(Objeto);
|
||||
|
||||
|
||||
// ✅ REMOVER de la lista inmediatamente para evitar referencias colgantes
|
||||
Cuerpos.Remove(Objeto);
|
||||
|
||||
if (Is3DUpdateEnabled)
|
||||
// Aplazar la eliminación para evitar problemas de concurrencia
|
||||
_deferredActions.Add(() =>
|
||||
{
|
||||
Visualization3DManager?.SynchronizeWorld();
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[Remove] ✅ Objeto marcado para eliminación diferida: {Objeto.GetType().Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[Remove] ❌ Error marcando objeto {Objeto?.GetType().Name}: {ex.Message}");
|
||||
|
||||
// ✅ CRÍTICO: Siempre remover de la lista incluso si falló
|
||||
Cuerpos.Remove(Objeto);
|
||||
UnregisterObjectHandle(Objeto);
|
||||
Objeto.RemoverBody();
|
||||
Cuerpos.Remove(Objeto);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public simBotella AddCircle(float diameter, Vector2 position, float mass)
|
||||
public simBotella AddBotella(float diameter, Vector2 position, float mass)
|
||||
{
|
||||
var botella = new simBotella(simulation, _deferredActions, diameter, position, mass);
|
||||
Cuerpos.Add(botella);
|
||||
RegisterObjectHandle(botella); // ✅ NUEVO
|
||||
|
||||
if (Is3DUpdateEnabled)
|
||||
{
|
||||
Visualization3DManager?.SynchronizeWorld();
|
||||
}
|
||||
RegisterObjectHandle(botella);
|
||||
return botella;
|
||||
}
|
||||
|
||||
|
@ -960,223 +1131,6 @@ namespace CtrEditor.Simulacion
|
|||
return new BarreraData(false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Detiene motores de botellas que no están en contacto con elementos
|
||||
/// </summary>
|
||||
private void StopMotorsForBottlesNotInContact()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allBottles = Cuerpos.OfType<simBotella>().ToList();
|
||||
|
||||
foreach (var bottle in allBottles)
|
||||
{
|
||||
if (bottle != null && bottle.HasMotor && bottle.IsOnElement)
|
||||
{
|
||||
// ✅ VERIFICAR SI LA BOTELLA ESTÁ REALMENTE EN CONTACTO CON ALGÚN ELEMENTO
|
||||
bool isInContact = false;
|
||||
|
||||
// Verificar contacto con transportes
|
||||
foreach (var transport in Cuerpos.OfType<simTransporte>())
|
||||
{
|
||||
if (IsBottleInContactWithTransport(bottle, transport))
|
||||
{
|
||||
isInContact = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar contacto con curvas
|
||||
if (!isInContact)
|
||||
{
|
||||
foreach (var curve in Cuerpos.OfType<simCurve>())
|
||||
{
|
||||
if (IsBottleInContactWithCurve(bottle, curve))
|
||||
{
|
||||
isInContact = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ DETENER MOTOR SI NO ESTÁ EN CONTACTO
|
||||
if (!isInContact)
|
||||
{
|
||||
bottle.StopMotor();
|
||||
System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ⏹️ Detenido: {bottle.BodyHandle}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[StopMotorsForBottlesNotInContact] ❌ ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Verifica si una botella está en contacto con un transporte
|
||||
/// </summary>
|
||||
private bool IsBottleInContactWithTransport(simBotella bottle, simTransporte transport)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bottlePos = bottle.GetPosition();
|
||||
var transportPos = transport.GetPosition();
|
||||
var distance = Vector2.Distance(
|
||||
new Vector2(bottlePos.X, bottlePos.Y),
|
||||
new Vector2(transportPos.X, transportPos.Y)
|
||||
);
|
||||
|
||||
// Verificar si está dentro del área del transporte + tolerancia
|
||||
var maxDistance = Math.Max(transport.Width, transport.Height) / 2f + bottle.Radius + 0.1f;
|
||||
return distance <= maxDistance;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Verifica si una botella está en contacto con una curva
|
||||
/// </summary>
|
||||
private bool IsBottleInContactWithCurve(simBotella bottle, simCurve curve)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bottlePos = bottle.GetPosition();
|
||||
var curveCenter = curve.CurveCenter; // ✅ NUEVO: Usar centro real
|
||||
var distance = Vector2.Distance(
|
||||
new Vector2(bottlePos.X, bottlePos.Y),
|
||||
new Vector2(curveCenter.X, curveCenter.Y)
|
||||
);
|
||||
|
||||
// Verificar si está dentro del área de la curva + tolerancia
|
||||
var maxDistance = curve.OuterRadius + bottle.Radius + 0.1f;
|
||||
return distance <= maxDistance;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Calcula la dirección tangencial específica basada en la posición de la botella
|
||||
/// </summary>
|
||||
public Vector3 CalculateCurveDirectionFromBottlePosition(simCurve curve, simBotella bottle)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ NUEVO: Usar el centro real almacenado de la curva
|
||||
var curveCenter = curve.CurveCenter;
|
||||
var bottlePosition = bottle.GetPosition();
|
||||
|
||||
// Calcular el vector desde el centro de la curva hasta la botella (en el plano XY)
|
||||
var radiusVector = new Vector3(bottlePosition.X - curveCenter.X, bottlePosition.Y - curveCenter.Y, 0f);
|
||||
var radius = radiusVector.Length();
|
||||
|
||||
if (radius < 0.001f)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ⚠️ Botella muy cerca del centro de la curva");
|
||||
return Vector3.UnitX; // Fallback
|
||||
}
|
||||
|
||||
// Normalizar el vector radial
|
||||
var normalizedRadius = radiusVector / radius;
|
||||
|
||||
// Calcular la dirección tangencial (perpendicular al radio)
|
||||
// En coordenadas 2D: si r = (x, y), entonces t = (-y, x) es tangente
|
||||
var tangentDirection = new Vector3(-normalizedRadius.Y, normalizedRadius.X, 0f);
|
||||
|
||||
// Verificar que la dirección tangencial apunte en el sentido correcto según la velocidad de la curva
|
||||
if (curve.Speed < 0)
|
||||
{
|
||||
//tangentDirection = -tangentDirection;
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] 📐 Dirección calculada:");
|
||||
//System.Diagnostics.Debug.WriteLine($" Centro curva: {curveCenter}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Posición botella: {bottlePosition}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Radio: {radius:F3}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Vector radial: {normalizedRadius}");
|
||||
//System.Diagnostics.Debug.WriteLine($" Dirección tangencial: {tangentDirection} (Longitud: {tangentDirection.Length():F3})");
|
||||
|
||||
return -tangentDirection;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[CalculateCurveDirectionFromBottlePosition] ❌ ERROR: {ex.Message}");
|
||||
return Vector3.UnitX; // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
simulation?.Dispose();
|
||||
bufferPool?.Clear();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registra un contacto entre una barrera y una botella para detección de paso
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera que detecta el paso</param>
|
||||
/// <param name="botella">Botella que está pasando</param>
|
||||
internal void RegisterBarreraContact(simBarrera barrera, simBotella botella)
|
||||
{
|
||||
if (barrera != null && botella != null)
|
||||
{
|
||||
lock (_contactsLock)
|
||||
{
|
||||
if (!_barreraContacts.ContainsKey(barrera))
|
||||
{
|
||||
_barreraContacts[barrera] = new List<simBotella>();
|
||||
}
|
||||
|
||||
if (!_barreraContacts[barrera].Contains(botella))
|
||||
{
|
||||
_barreraContacts[barrera].Add(botella);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registra un contacto entre un descarte y una botella para marcar eliminación
|
||||
/// </summary>
|
||||
/// <param name="descarte">Descarte que detecta la botella</param>
|
||||
/// <param name="botella">Botella que debe ser eliminada</param>
|
||||
public void RegisterDescarteContact(simDescarte descarte, simBotella botella)
|
||||
{
|
||||
if (descarte != null && botella != null)
|
||||
{
|
||||
lock (_contactsLock)
|
||||
{
|
||||
// Marcar la botella para eliminación
|
||||
_botellasParaEliminar.Add(botella);
|
||||
botella.Descartar = true;
|
||||
|
||||
// También registrar el contacto para la lista del descarte
|
||||
if (!_descarteContacts.ContainsKey(descarte))
|
||||
{
|
||||
_descarteContacts[descarte] = new List<simBotella>();
|
||||
}
|
||||
|
||||
if (!_descarteContacts[descarte].Contains(botella))
|
||||
{
|
||||
_descarteContacts[descarte].Add(botella);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Procesa todas las barreras usando RayCast de BEPU
|
||||
/// Elimina la dependencia de contactos físicos y la necesidad de mantener botellas despiertas
|
||||
|
@ -1219,9 +1173,6 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
}
|
||||
|
||||
// ✅ ELIMINADO: CalculateBarreraDetectionGeometric - ya no se usa
|
||||
// El nuevo sistema usa RayCast nativo de BEPU a través de simBarrera.PerformRaycast()
|
||||
|
||||
/// <summary>
|
||||
/// Procesa contactos de descarte para eliminar botellas marcadas usando detección geométrica
|
||||
/// </summary>
|
||||
|
@ -1258,12 +1209,6 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
// Eliminar botellas marcadas para eliminación (después de procesamiento)
|
||||
RemoveMarkedBottles();
|
||||
|
||||
// Limpiar contactos ya que no los usamos más
|
||||
lock (_contactsLock)
|
||||
{
|
||||
_descarteContacts.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1361,6 +1306,15 @@ namespace CtrEditor.Simulacion
|
|||
List<simBotella> botellasAEliminar;
|
||||
lock (_contactsLock)
|
||||
{
|
||||
// ✅ NUEVO: Añadir botellas marcadas como "Descartar" desde los callbacks
|
||||
foreach (var botella in Cuerpos.OfType<simBotella>())
|
||||
{
|
||||
if (botella.Descartar)
|
||||
{
|
||||
_botellasParaEliminar.Add(botella);
|
||||
}
|
||||
}
|
||||
|
||||
botellasAEliminar = new List<simBotella>(_botellasParaEliminar);
|
||||
_botellasParaEliminar.Clear();
|
||||
}
|
||||
|
@ -1459,5 +1413,36 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
return lineStart + lineDirection * projectionLength;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
simulation?.Dispose();
|
||||
bufferPool?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registra un contacto entre una barrera y una botella para detección de paso
|
||||
/// </summary>
|
||||
/// <param name="barrera">Barrera que detecta el paso</param>
|
||||
/// <param name="botella">Botella que está pasando</param>
|
||||
internal void RegisterBarreraContact(simBarrera barrera, simBotella botella)
|
||||
{
|
||||
if (barrera != null && botella != null)
|
||||
{
|
||||
lock (_contactsLock)
|
||||
{
|
||||
if (!_barreraContacts.ContainsKey(barrera))
|
||||
{
|
||||
_barreraContacts[barrera] = new List<simBotella>();
|
||||
}
|
||||
|
||||
if (!_barreraContacts[barrera].Contains(botella))
|
||||
{
|
||||
_barreraContacts[barrera].Add(botella);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ using BepuPhysics.Collidables;
|
|||
using HelixToolkit.Wpf;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
@ -28,6 +30,9 @@ namespace CtrEditor.Simulacion
|
|||
public float StartAngle { get; set; }
|
||||
public float EndAngle { get; set; }
|
||||
|
||||
// ✅ NUEVO: Estado de movimiento para transportes
|
||||
public bool IsMoving { get; set; }
|
||||
|
||||
public bool Equals(ShapeDimensions other)
|
||||
{
|
||||
return Math.Abs(Width - other.Width) < 0.001f &&
|
||||
|
@ -38,7 +43,8 @@ namespace CtrEditor.Simulacion
|
|||
Math.Abs(OuterRadius - other.OuterRadius) < 0.001f &&
|
||||
Math.Abs(StartAngle - other.StartAngle) < 0.001f &&
|
||||
Math.Abs(EndAngle - other.EndAngle) < 0.001f &&
|
||||
ShapeType == other.ShapeType;
|
||||
ShapeType == other.ShapeType &&
|
||||
IsMoving == other.IsMoving;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,8 +77,11 @@ namespace CtrEditor.Simulacion
|
|||
private Dictionary<simBase, ModelVisual3D> simBaseToModelMap;
|
||||
private Dictionary<simBase, ShapeDimensions> lastKnownDimensions;
|
||||
|
||||
// ✅ NUEVO: Diccionario para gestionar animaciones activas
|
||||
private Dictionary<simBase, Storyboard> activeAnimations;
|
||||
|
||||
// ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar)
|
||||
public static bool DebugShowIndividualTriangles { get; set; } = true;
|
||||
public static bool DebugShowIndividualTriangles { get; set; } = false;
|
||||
|
||||
public HelixViewport3D Viewport3D
|
||||
{
|
||||
|
@ -92,6 +101,7 @@ namespace CtrEditor.Simulacion
|
|||
simulationManager = simManager;
|
||||
simBaseToModelMap = new Dictionary<simBase, ModelVisual3D>();
|
||||
lastKnownDimensions = new Dictionary<simBase, ShapeDimensions>();
|
||||
activeAnimations = new Dictionary<simBase, Storyboard>();
|
||||
|
||||
InitializeViewport();
|
||||
}
|
||||
|
@ -112,14 +122,14 @@ namespace CtrEditor.Simulacion
|
|||
// Agregar luces
|
||||
var directionalLight = new DirectionalLight
|
||||
{
|
||||
Color = Colors.White,
|
||||
Direction = new Vector3D(0, 0, -1)
|
||||
Color = Color.FromRgb(150, 150, 150), // Luz direccional menos intensa
|
||||
Direction = new Vector3D(-0.2, -0.3, -1) // Ligeramente angulada para evitar reflejos directos
|
||||
};
|
||||
viewport3D.Children.Add(new ModelVisual3D { Content = directionalLight });
|
||||
|
||||
var ambientLight = new AmbientLight
|
||||
{
|
||||
Color = Color.FromRgb(64, 64, 64)
|
||||
Color = Color.FromRgb(120, 120, 120) // Más luz ambiente para suavizar sombras
|
||||
};
|
||||
viewport3D.Children.Add(new ModelVisual3D { Content = ambientLight });
|
||||
|
||||
|
@ -243,6 +253,12 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
foreach (var simObj in objectsToRemove)
|
||||
{
|
||||
// ✅ NUEVO: Limpiar animaciones activas para objetos eliminados
|
||||
if (simObj is simTransporte transporteToRemove && activeAnimations.ContainsKey(transporteToRemove))
|
||||
{
|
||||
StopTransportAnimation(transporteToRemove);
|
||||
}
|
||||
|
||||
simBaseToModelMap.Remove(simObj);
|
||||
lastKnownDimensions.Remove(simObj);
|
||||
}
|
||||
|
@ -397,7 +413,7 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
else
|
||||
{
|
||||
// ✅ Usar directamente los triángulos originales de BEPU (superficie continua)
|
||||
// ✅ CORREGIDO: Crea un arco de mesh continuo y simple desde los triángulos de BEPU
|
||||
CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
|
||||
}
|
||||
|
||||
|
@ -422,44 +438,57 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ CORREGIDO: Crea mesh desde los triángulos locales de BEPU
|
||||
/// Extrae la geometría exacta en coordenadas locales para evitar transformación duplicada
|
||||
/// ✅ CORREGIDO: Crea un arco de mesh continuo y simple desde los triángulos de BEPU
|
||||
/// Genera una superficie plana que forma un arco suave y eficiente
|
||||
/// </summary>
|
||||
private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ✅ EXTRAER TRIÁNGULOS LOCALES DE BEPU (sin transformación duplicada)
|
||||
// Obtener los triángulos de BEPU
|
||||
var localTriangles = curve.GetRealBEPUTriangles();
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] Creating continuous arc mesh from {localTriangles.Length} BEPU triangles");
|
||||
|
||||
if (localTriangles.Length == 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D BEPU] WARNING: No se pudieron extraer triángulos locales, usando fallback");
|
||||
CreateCurveMeshFallback(meshBuilder, curve);
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] WARNING: No triangles found in BEPU curve");
|
||||
return;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {localTriangles.Length} triángulos locales de BEPU");
|
||||
// ✅ CORREGIDO: Usar altura más visible y verificar coordenadas
|
||||
const float curveHeight = 0.0f; // 0.02f; // 2cm de altura para que sea claramente visible
|
||||
int triangleCount = 0;
|
||||
|
||||
// ✅ USAR TRIÁNGULOS LOCALES DE BEPU (la transformación se aplicará automáticamente en UpdateVisualization)
|
||||
foreach (var triangle in localTriangles)
|
||||
{
|
||||
// Convertir triángulos de BEPU a puntos 3D de Helix
|
||||
var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z);
|
||||
var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z);
|
||||
var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z);
|
||||
// ✅ LOGGING: Verificar las coordenadas del primer triángulo
|
||||
if (triangleCount == 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: A({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})");
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: B({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})");
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: C({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})");
|
||||
}
|
||||
|
||||
// Agregar triángulo en coordenadas locales al mesh
|
||||
// Crear triángulo en la superficie superior con altura visible
|
||||
var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z + curveHeight);
|
||||
var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z + curveHeight);
|
||||
var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z + curveHeight);
|
||||
|
||||
// ✅ CORREGIDO: Probar ambos órdenes de vértices para asegurar normales correctas
|
||||
meshBuilder.AddTriangle(pointA, pointB, pointC);
|
||||
// También agregar el triángulo con orden inverso para que sea visible desde ambos lados
|
||||
meshBuilder.AddTriangle(pointA, pointC, pointB);
|
||||
|
||||
triangleCount++;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos locales de BEPU (transformación aplicada automáticamente)");
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] ✅ Continuous arc mesh created with {triangleCount} triangles (height: {curveHeight})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos locales: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Fallback a geometría recreada");
|
||||
CreateCurveMeshFallback(meshBuilder, curve);
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] ERROR creating continuous arc mesh: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Curve] Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,7 +506,7 @@ namespace CtrEditor.Simulacion
|
|||
if (localTriangles.Length == 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No hay triángulos locales para debug, usando fallback");
|
||||
CreateCurveMeshFallback(meshBuilder, curve);
|
||||
CreateBasicDebugGeometry(meshBuilder, curve);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -574,81 +603,6 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Método fallback que recrea la geometría (mantener por compatibilidad)
|
||||
/// </summary>
|
||||
private void CreateCurveMeshFallback(MeshBuilder meshBuilder, simCurve curve)
|
||||
{
|
||||
// Obtener parámetros de la curva
|
||||
float innerRadius = curve.InnerRadius;
|
||||
float outerRadius = curve.OuterRadius;
|
||||
float startAngle = curve.StartAngle;
|
||||
float endAngle = curve.EndAngle;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Fallback] Parameters - Inner: {innerRadius}, Outer: {outerRadius}, Start: {startAngle}, End: {endAngle}");
|
||||
|
||||
// Configuración de segmentos
|
||||
const float SegmentationFactor = 32f / 3f;
|
||||
const int MinSegments = 8;
|
||||
const int MaxSegments = 64;
|
||||
|
||||
// Calcular número de segmentos basado en el tamaño del arco
|
||||
float arcLength = (endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
|
||||
int segments = (int)(arcLength * SegmentationFactor);
|
||||
segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments));
|
||||
|
||||
float angleStep = (endAngle - startAngle) / segments;
|
||||
|
||||
// Altura muy pequeña para simular triángulos planos
|
||||
const float curveHeight = 0.05f;
|
||||
|
||||
// Generar vértices para el arco interior y exterior
|
||||
var innerBottomPoints = new Point3D[segments + 1];
|
||||
var innerTopPoints = new Point3D[segments + 1];
|
||||
var outerBottomPoints = new Point3D[segments + 1];
|
||||
var outerTopPoints = new Point3D[segments + 1];
|
||||
|
||||
for (int i = 0; i <= segments; i++)
|
||||
{
|
||||
float angle = startAngle + i * angleStep;
|
||||
float cosAngle = (float)Math.Cos(angle);
|
||||
float sinAngle = (float)Math.Sin(angle);
|
||||
|
||||
// Puntos en la parte inferior (Z = 0)
|
||||
innerBottomPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, 0);
|
||||
outerBottomPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, 0);
|
||||
|
||||
// Puntos en la parte superior (Z = curveHeight)
|
||||
innerTopPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, curveHeight);
|
||||
outerTopPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, curveHeight);
|
||||
}
|
||||
|
||||
// Crear la superficie superior de la curva
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i], outerTopPoints[i + 1]);
|
||||
meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i + 1], innerTopPoints[i + 1]);
|
||||
}
|
||||
|
||||
// Crear la superficie inferior de la curva
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
meshBuilder.AddTriangle(innerBottomPoints[i], outerBottomPoints[i + 1], outerBottomPoints[i]);
|
||||
meshBuilder.AddTriangle(innerBottomPoints[i], innerBottomPoints[i + 1], outerBottomPoints[i + 1]);
|
||||
}
|
||||
|
||||
// Crear las paredes laterales
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
meshBuilder.AddQuad(innerBottomPoints[i], innerBottomPoints[i + 1], innerTopPoints[i + 1], innerTopPoints[i]);
|
||||
meshBuilder.AddQuad(outerBottomPoints[i], outerTopPoints[i], outerTopPoints[i + 1], outerBottomPoints[i + 1]);
|
||||
}
|
||||
|
||||
// Crear las paredes de los extremos
|
||||
meshBuilder.AddQuad(innerBottomPoints[0], innerTopPoints[0], outerTopPoints[0], outerBottomPoints[0]);
|
||||
meshBuilder.AddQuad(outerBottomPoints[segments], outerTopPoints[segments], innerTopPoints[segments], innerBottomPoints[segments]);
|
||||
}
|
||||
|
||||
private void UpdateVisualizationFromSimBase(simBase simObj)
|
||||
{
|
||||
if (!simBaseToModelMap.TryGetValue(simObj, out var visual))
|
||||
|
@ -688,6 +642,12 @@ namespace CtrEditor.Simulacion
|
|||
// System.Diagnostics.Debug.WriteLine($"[3D Update] No geometry changes needed for {simObj.GetType().Name}");
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Gestionar animaciones para transportes
|
||||
if (simObj is simTransporte transporte)
|
||||
{
|
||||
ManageTransportAnimation(transporte, visual);
|
||||
}
|
||||
|
||||
// Actualizar transformación del modelo 3D
|
||||
var transform = new Transform3DGroup();
|
||||
|
||||
|
@ -751,6 +711,12 @@ namespace CtrEditor.Simulacion
|
|||
return dimensions;
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Para simTransporte, detectar si está en movimiento
|
||||
if (simObj is simTransporte transporte)
|
||||
{
|
||||
dimensions.IsMoving = Math.Abs(transporte.Speed) > 0.001f; // Consideramos movimiento si la velocidad es mayor que 0.001
|
||||
}
|
||||
|
||||
// Para otros objetos, usar la forma real de BEPU
|
||||
if (shapeIndex.Type == BepuPhysics.Collidables.Sphere.Id)
|
||||
{
|
||||
|
@ -868,6 +834,13 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
public void Clear()
|
||||
{
|
||||
// ✅ NUEVO: Detener todas las animaciones activas antes de limpiar
|
||||
foreach (var kvp in activeAnimations)
|
||||
{
|
||||
kvp.Value.Stop();
|
||||
}
|
||||
activeAnimations.Clear();
|
||||
|
||||
foreach (var model in simBaseToModelMap.Values)
|
||||
{
|
||||
viewport3D.Children.Remove(model);
|
||||
|
@ -895,6 +868,40 @@ namespace CtrEditor.Simulacion
|
|||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea un material animable (no frozen) para poder animar sus propiedades
|
||||
/// </summary>
|
||||
/// <param name="color">Color del material</param>
|
||||
/// <param name="specularPower">Potencia especular</param>
|
||||
/// <param name="ambient">Nivel de luz ambiente</param>
|
||||
/// <returns>Material que puede ser animado</returns>
|
||||
private Material CreateAnimatableMaterial(Color color, double specularPower = 100, byte ambient = 190)
|
||||
{
|
||||
// Crear brush animable (no frozen)
|
||||
var diffuseBrush = new SolidColorBrush(color);
|
||||
|
||||
// Crear material difuso
|
||||
var diffuseMaterial = new DiffuseMaterial(diffuseBrush);
|
||||
|
||||
// Crear material especular - ✅ CORREGIDO: Usar un gris claro en lugar de blanco puro para reducir el brillo excesivo
|
||||
var specularBrush = new SolidColorBrush(Color.FromRgb(128, 128, 128));
|
||||
var specularMaterial = new SpecularMaterial(specularBrush, specularPower);
|
||||
|
||||
// Crear material "ambiente" (usando Emissive) - ✅ CORREGIDO: Usar un color mucho más oscuro para no sobreexponer
|
||||
var ambientColor = Color.FromRgb((byte)(ambient / 4), (byte)(ambient / 4), (byte)(ambient / 4));
|
||||
var ambientBrush = new SolidColorBrush(ambientColor);
|
||||
var ambientMaterial = new EmissiveMaterial(ambientBrush);
|
||||
|
||||
// Combinar todos los materiales
|
||||
var materialGroup = new MaterialGroup();
|
||||
materialGroup.Children.Add(diffuseMaterial);
|
||||
materialGroup.Children.Add(specularMaterial);
|
||||
materialGroup.Children.Add(ambientMaterial);
|
||||
|
||||
// NO hacer freeze para mantener animable
|
||||
return materialGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Función centralizada para obtener el material apropiado según el tipo de simBase
|
||||
/// </summary>
|
||||
|
@ -902,14 +909,24 @@ namespace CtrEditor.Simulacion
|
|||
/// <returns>Material configurado para el tipo de objeto</returns>
|
||||
private Material GetMaterialForSimBase(simBase simObj)
|
||||
{
|
||||
if (simObj is simTransporte)
|
||||
if (simObj is simTransporte transporte)
|
||||
{
|
||||
// Material plástico verde para transportes
|
||||
return MaterialHelper.CreateMaterial(
|
||||
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
|
||||
specularPower: 140,
|
||||
ambient: 200
|
||||
);
|
||||
// ✅ NUEVO: Cambiar color según si está en movimiento
|
||||
bool isMoving = Math.Abs(transporte.Speed) > 0.001f;
|
||||
if (isMoving)
|
||||
{
|
||||
// ✅ CORREGIDO: Usar material animable para transportes en movimiento
|
||||
return CreateAnimatableMaterial(Color.FromRgb(40, 255, 40), 180, 240);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Material plástico verde normal para transportes detenidos
|
||||
return MaterialHelper.CreateMaterial(
|
||||
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
|
||||
specularPower: 140,
|
||||
ambient: 200
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (simObj is simGuia)
|
||||
{
|
||||
|
@ -1002,6 +1019,150 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Crea una animación de movimiento para transportes usando StoryBoard
|
||||
/// Combina rotación sutil + pulsación de brillo para simular movimiento
|
||||
/// </summary>
|
||||
/// <param name="transporte">Transporte al cual aplicar la animación</param>
|
||||
/// <param name="visual">Visual3D del transporte</param>
|
||||
private void CreateTransportMovementAnimation(simTransporte transporte, ModelVisual3D visual)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Detener animación anterior si existe
|
||||
StopTransportAnimation(transporte);
|
||||
|
||||
// Crear nuevo StoryBoard
|
||||
var storyboard = new Storyboard();
|
||||
storyboard.RepeatBehavior = RepeatBehavior.Forever;
|
||||
|
||||
// ✅ ANIMACIÓN 1: Rotación sutil continua alrededor del eje Z (muy lenta)
|
||||
var rotationAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 360,
|
||||
Duration = TimeSpan.FromSeconds(20), // 20 segundos por vuelta completa (muy lento)
|
||||
RepeatBehavior = RepeatBehavior.Forever
|
||||
};
|
||||
|
||||
// Crear transform group si no existe
|
||||
if (visual.Transform is not Transform3DGroup transformGroup)
|
||||
{
|
||||
transformGroup = new Transform3DGroup();
|
||||
if (visual.Transform != null)
|
||||
transformGroup.Children.Add(visual.Transform);
|
||||
visual.Transform = transformGroup;
|
||||
}
|
||||
|
||||
// Agregar rotación de animación
|
||||
var animationRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0));
|
||||
transformGroup.Children.Add(animationRotation);
|
||||
|
||||
// Configurar target para la rotación
|
||||
Storyboard.SetTarget(rotationAnimation, animationRotation);
|
||||
Storyboard.SetTargetProperty(rotationAnimation, new PropertyPath("Rotation.Angle"));
|
||||
storyboard.Children.Add(rotationAnimation);
|
||||
|
||||
// ✅ ANIMACIÓN 2: Pulsación de brillo del material
|
||||
if (visual.Content is GeometryModel3D geometryModel &&
|
||||
geometryModel.Material is MaterialGroup materialGroup)
|
||||
{
|
||||
// Buscar el material difuso para animar (debe ser el primero en mi CreateAnimatableMaterial)
|
||||
var diffuseMaterial = materialGroup.Children.OfType<DiffuseMaterial>().FirstOrDefault();
|
||||
if (diffuseMaterial?.Brush is SolidColorBrush diffuseBrush)
|
||||
{
|
||||
// ✅ CORREGIDO: El brush ya debe ser animable si se creó con CreateAnimatableMaterial
|
||||
var colorAnimation = new ColorAnimation
|
||||
{
|
||||
From = Color.FromRgb(40, 255, 40), // Verde brillante
|
||||
To = Color.FromRgb(80, 255, 80), // Verde más brillante
|
||||
Duration = TimeSpan.FromSeconds(1.5), // Pulsación cada 1.5 segundos
|
||||
AutoReverse = true,
|
||||
RepeatBehavior = RepeatBehavior.Forever
|
||||
};
|
||||
|
||||
Storyboard.SetTarget(colorAnimation, diffuseBrush);
|
||||
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("Color"));
|
||||
storyboard.Children.Add(colorAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
// Iniciar animación
|
||||
storyboard.Begin();
|
||||
|
||||
// Almacenar referencia para poder detenerla después
|
||||
activeAnimations[transporte] = storyboard;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de movimiento creada para transporte");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error creando animación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Detiene la animación de movimiento de un transporte
|
||||
/// </summary>
|
||||
/// <param name="transporte">Transporte cuya animación se desea detener</param>
|
||||
private void StopTransportAnimation(simTransporte transporte)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (activeAnimations.TryGetValue(transporte, out var storyboard))
|
||||
{
|
||||
storyboard.Stop();
|
||||
activeAnimations.Remove(transporte);
|
||||
|
||||
// Limpiar transforms de animación del visual
|
||||
if (simBaseToModelMap.TryGetValue(transporte, out var visual) &&
|
||||
visual.Transform is Transform3DGroup transformGroup)
|
||||
{
|
||||
// Remover solo la rotación de animación (la última agregada)
|
||||
if (transformGroup.Children.Count > 1)
|
||||
{
|
||||
transformGroup.Children.RemoveAt(transformGroup.Children.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de transporte detenida");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error deteniendo animación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Gestiona automáticamente las animaciones de transportes según su estado
|
||||
/// </summary>
|
||||
/// <param name="transporte">Transporte a gestionar</param>
|
||||
/// <param name="visual">Visual3D del transporte</param>
|
||||
private void ManageTransportAnimation(simTransporte transporte, ModelVisual3D visual)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool isMoving = Math.Abs(transporte.Speed) > 0.001f;
|
||||
bool hasActiveAnimation = activeAnimations.ContainsKey(transporte);
|
||||
|
||||
if (isMoving && !hasActiveAnimation)
|
||||
{
|
||||
// Crear animación si el transporte está en movimiento y no tiene animación
|
||||
CreateTransportMovementAnimation(transporte, visual);
|
||||
}
|
||||
else if (!isMoving && hasActiveAnimation)
|
||||
{
|
||||
// Detener animación si el transporte está detenido y tiene animación
|
||||
StopTransportAnimation(transporte);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error gestionando animación: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ MODO DEBUG DE TRIÁNGULOS DE CURVAS:
|
||||
///
|
||||
|
@ -1372,7 +1533,8 @@ namespace CtrEditor.Simulacion
|
|||
InnerRadius = 0,
|
||||
OuterRadius = 0,
|
||||
StartAngle = 0,
|
||||
EndAngle = 0
|
||||
EndAngle = 0,
|
||||
IsMoving = false // Las barreras no se mueven
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
public class simBase
|
||||
{
|
||||
public object WpfObject { get; set; }
|
||||
public string SimObjectType { get; set; }
|
||||
public BodyHandle BodyHandle { get; protected set; }
|
||||
public Simulation _simulation;
|
||||
protected bool _bodyCreated = false; // Bandera para saber si hemos creado un cuerpo
|
||||
|
|
|
@ -8,6 +8,8 @@ using System.Numerics;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace CtrEditor.Simulacion
|
||||
{
|
||||
|
@ -29,17 +31,14 @@ namespace CtrEditor.Simulacion
|
|||
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
|
||||
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
|
||||
|
||||
// ✅ 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;
|
||||
// ✅ NUEVO: Propiedades para control de flotación/presión
|
||||
public float? LastTransportCollisionZ { get; set; }
|
||||
public float PressureBuildup { get; set; } = 0;
|
||||
public bool IsTouchingTransport { get; set; } = false;
|
||||
|
||||
// ✅ 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
|
||||
// ✅ NUEVO: Propiedades para la física personalizada
|
||||
public float BaseInverseMass { get; private set; }
|
||||
public Vector3 InitialPosition3D { get; private set; }
|
||||
|
||||
private List<Action> _deferredActions;
|
||||
|
||||
|
@ -56,8 +55,6 @@ namespace CtrEditor.Simulacion
|
|||
// ✅ 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
|
||||
|
@ -121,9 +118,16 @@ namespace CtrEditor.Simulacion
|
|||
_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);
|
||||
var cylinder = new Cylinder(Radius, Height);
|
||||
var inertia = cylinder.ComputeInertia(_mass);
|
||||
|
||||
// Modificamos el tensor de inercia para evitar que la botella vuelque.
|
||||
var inverseInertia = inertia.InverseInertiaTensor;
|
||||
inverseInertia.XX = 0f; // Resistencia infinita a la rotación en el eje X (vuelco).
|
||||
inverseInertia.YY = 0f; // Resistencia infinita a la rotación en el eje Y (vuelco).
|
||||
// La resistencia en ZZ (eje Z) se mantiene, permitiendo que la botella gire sobre su base.
|
||||
inertia.InverseInertiaTensor = inverseInertia;
|
||||
|
||||
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||
}
|
||||
}
|
||||
|
@ -133,324 +137,41 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
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 cylinder = new Cylinder(Radius, Height);
|
||||
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||
|
||||
// 1. Creamos el cuerpo con una inercia por defecto.
|
||||
var defaultInertia = cylinder.ComputeInertia(_mass);
|
||||
var activityDescription = new BodyActivityDescription(-1f);
|
||||
var bodyDescription = BodyDescription.CreateDynamic(
|
||||
new RigidPose(position),
|
||||
new BodyVelocity(),
|
||||
inertia, // Inercia estándar de esfera
|
||||
new CollidableDescription(shapeIndex, 0.001f),
|
||||
defaultInertia,
|
||||
new CollidableDescription(shapeIndex, 0),
|
||||
activityDescription
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true; // Marcar que hemos creado un cuerpo
|
||||
_bodyCreated = true;
|
||||
|
||||
// 2. Inmediatamente después de crearlo, aplicamos la inercia personalizada.
|
||||
// Esto asegura que SetLocalInertia se llame para todos los objetos nuevos.
|
||||
var inertia = cylinder.ComputeInertia(_mass);
|
||||
var inverseInertia = inertia.InverseInertiaTensor;
|
||||
inverseInertia.XX = 0f;
|
||||
inverseInertia.YY = 0f;
|
||||
inertia.InverseInertiaTensor = inverseInertia;
|
||||
_simulation.Bodies.SetLocalInertia(BodyHandle, inertia);
|
||||
|
||||
// ✅ NUEVO: Almacenar la masa inversa base después de crear el cuerpo
|
||||
// Esto nos sirve como referencia para la lógica de masa dinámica.
|
||||
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
this.BaseInverseMass = bodyReference.LocalInertia.InverseMass;
|
||||
|
||||
// ✅ CORREGIDO: Usar el diccionario del SimulationManager y la clave .Value del handle.
|
||||
if (_simulationManager != null)
|
||||
_simulationManager.CollidableData[BodyHandle.Value] = this;
|
||||
}
|
||||
|
||||
/// <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.SpeedConversionFactor);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ CREAR NUEVO MOTOR SI ES NECESARIO
|
||||
if (needsNewMotor && target != null)
|
||||
{
|
||||
CreateMotorForTarget(target, direction, speed / simBase.SpeedConversionFactor);
|
||||
}
|
||||
}
|
||||
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), 0f)
|
||||
};
|
||||
|
||||
// ✅ 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;
|
||||
|
@ -459,8 +180,8 @@ namespace CtrEditor.Simulacion
|
|||
// ✅ 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);
|
||||
var cylinder = new Cylinder(Radius, Height);
|
||||
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
@ -472,8 +193,8 @@ namespace CtrEditor.Simulacion
|
|||
// ✅ 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);
|
||||
var cylinder = new Cylinder(Radius, Height);
|
||||
var shapeIndex = _simulation.Shapes.Add(cylinder);
|
||||
ChangeBodyShape(shapeIndex);
|
||||
}
|
||||
}
|
||||
|
@ -499,8 +220,8 @@ namespace CtrEditor.Simulacion
|
|||
// 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);
|
||||
// ✅ CORREGIDO: Calificar explícitamente para evitar ambigüedad de tipos.
|
||||
var correctedOrientation = System.Numerics.Quaternion.CreateFromAxisAngle(Vector3.UnitZ, rotationZ);
|
||||
|
||||
// Aplicar la orientación corregida
|
||||
bodyReference.Pose.Orientation = correctedOrientation;
|
||||
|
|
|
@ -32,9 +32,6 @@ namespace CtrEditor.Simulacion
|
|||
// ✅ 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: Almacenar triángulos creados para acceso directo
|
||||
private Triangle[] _storedTriangles;
|
||||
|
||||
|
@ -66,11 +63,37 @@ namespace CtrEditor.Simulacion
|
|||
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
|
||||
}
|
||||
|
||||
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
|
||||
// ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed; // Velocidad angular directa (sin inversión)
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
Speed = speed;
|
||||
ActualizarVelocidadCinematica();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Aplica la velocidad angular al cuerpo cinemático de BEPU.
|
||||
/// </summary>
|
||||
public void ActualizarVelocidadCinematica()
|
||||
{
|
||||
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
|
||||
{
|
||||
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
|
||||
float effectiveRadius = (_innerRadius + _outerRadius) / 2.0f;
|
||||
if (effectiveRadius > 0.001f)
|
||||
{
|
||||
// La velocidad tangencial (Speed) se convierte a velocidad angular (ω = v / r)
|
||||
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||
float angularSpeed = (Speed / SpeedConversionFactor) / effectiveRadius;
|
||||
|
||||
// La rotación es alrededor del eje Z en el sistema de coordenadas de BEPU
|
||||
body.Velocity.Angular = new Vector3(0, 0, angularSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Velocity.Angular = Vector3.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -220,19 +243,8 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
@ -242,7 +254,7 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
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
|
||||
// ✅ CORREGIDO: Como Aether - startAngle y endAngle definen directamente el sector
|
||||
// No hay rotación separada del objeto
|
||||
|
||||
// Actualizar parámetros internos
|
||||
|
@ -253,53 +265,45 @@ namespace CtrEditor.Simulacion
|
|||
_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
|
||||
Create(_curveCenter); // Llamar a la sobrecarga privada
|
||||
}
|
||||
|
||||
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)
|
||||
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");
|
||||
System.Diagnostics.Debug.WriteLine($"[simCurve] No se crearon triángulos, no se creará el cuerpo.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
_storedTriangles = triangles;
|
||||
|
||||
// ✅ 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
|
||||
// ✅ CORREGIDO: Convertir el array de triángulos a un Buffer<Triangle> de BEPU.
|
||||
_simulation.BufferPool.Take(triangles.Length, out BepuUtilities.Memory.Buffer<Triangle> triangleBuffer);
|
||||
for (int i = 0; i < triangles.Length; i++)
|
||||
{
|
||||
triangleBuffer[i] = triangles[i];
|
||||
}
|
||||
var mesh = new Mesh(triangleBuffer, Vector3.One, _simulation.BufferPool);
|
||||
var shapeIndex = _simulation.Shapes.Add(mesh);
|
||||
|
||||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(position, Quaternion.Identity), // La rotación se maneja con la velocidad angular, no con la pose inicial
|
||||
new CollidableDescription(shapeIndex, 0.1f),
|
||||
new BodyActivityDescription(-1f) // ✅ CORREGIDO: -1f para que el cuerpo cinemático NUNCA duerma y no se mueva por su cuenta.
|
||||
);
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
_bodyCreated = true;
|
||||
|
||||
// ✅ NUEVO: Establecer la velocidad cinemática inicial al crear el cuerpo.
|
||||
ActualizarVelocidadCinematica();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
|
||||
|
@ -340,11 +344,5 @@ namespace CtrEditor.Simulacion
|
|||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,11 +26,6 @@ namespace CtrEditor.Simulacion
|
|||
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;
|
||||
|
@ -66,11 +61,8 @@ namespace CtrEditor.Simulacion
|
|||
{
|
||||
base.SetRotation(wpfAngle);
|
||||
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas después del cambio de rotación
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad 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)
|
||||
|
@ -108,14 +100,11 @@ namespace CtrEditor.Simulacion
|
|||
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
|
||||
// ✅ CRÍTICO: Actualizar propiedades cacheadas y velocidad 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
|
||||
// ✅ NUEVO MÉTODO - actualizar propiedades cacheadas y velocidad cinemática
|
||||
internal void UpdateCachedProperties()
|
||||
{
|
||||
// ✅ CORREGIDO: La dirección siempre es UnitX rotado por el ángulo del transporte
|
||||
|
@ -148,15 +137,29 @@ namespace CtrEditor.Simulacion
|
|||
// 🔍 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()}");
|
||||
}
|
||||
|
||||
// ✅ NUEVO: Actualizar la velocidad del cuerpo cinemático siempre que cambien las propiedades
|
||||
ActualizarVelocidadCinematica();
|
||||
}
|
||||
|
||||
// ✅ MODIFICAR MÉTODO EXISTENTE - disparar evento
|
||||
// ✅ MODIFICADO: Actualizar la velocidad y luego la del cuerpo cinemático
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
Speed = speed;
|
||||
UpdateCachedProperties();
|
||||
// Disparar evento para actualizar motores activos
|
||||
OnSpeedChanged?.Invoke(this);
|
||||
ActualizarVelocidadCinematica();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ NUEVO: Aplica la velocidad al cuerpo cinemático de BEPU.
|
||||
/// </summary>
|
||||
public void ActualizarVelocidadCinematica()
|
||||
{
|
||||
if (_simulation != null && this.BodyHandle.Value >= 0 && _simulation.Bodies.BodyExists(this.BodyHandle))
|
||||
{
|
||||
var body = _simulation.Bodies.GetBodyReference(BodyHandle);
|
||||
// ✅ CORREGIDO: Convertir velocidad de m/min a m/s para la simulación
|
||||
body.Velocity.Linear = this.DirectionVector * (this.Speed / SpeedConversionFactor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -190,9 +193,6 @@ namespace CtrEditor.Simulacion
|
|||
|
||||
// ✅ 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)
|
||||
|
@ -205,20 +205,10 @@ namespace CtrEditor.Simulacion
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// ✅ SIMPLIFICADO: RemoverBody que limpia restricciones y elimina el body
|
||||
/// ✅ SIMPLIFICADO: RemoverBody ya no necesita limpiar restricciones de botellas.
|
||||
/// </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();
|
||||
}
|
||||
|
||||
|
@ -233,7 +223,7 @@ namespace CtrEditor.Simulacion
|
|||
var bodyDescription = BodyDescription.CreateKinematic(
|
||||
new RigidPose(bepuPosition, CoordinateConverter.CreateBepuQuaternionFromWpfAngle(wpfAngle)),
|
||||
new CollidableDescription(shapeIndex, 0.1f),
|
||||
new BodyActivityDescription(0.01f)
|
||||
new BodyActivityDescription(-1f)
|
||||
);
|
||||
|
||||
BodyHandle = _simulation.Bodies.Add(bodyDescription);
|
||||
|
|
Loading…
Reference in New Issue