Se implementó un nuevo sistema de limitación de fuerzas para contactos múltiples simultáneos en la simulación de botellas, mejorando la estabilidad y realismo al evitar fuerzas armónicas irreales. Se introdujo un enfoque post-solver para controlar velocidades Z excesivas y se simplificó la lógica de integración de velocidad. Además, se ajustaron los parámetros de fricción y recuperación para optimizar el comportamiento en situaciones de contacto. Se añadieron nuevas funciones para el seguimiento de densidad de contactos y la calibración visual del deslizamiento.

This commit is contained in:
Miguel 2025-07-09 09:47:39 +02:00
parent 6887ede5e0
commit b44bdc5ece
4 changed files with 267 additions and 100 deletions

View File

@ -34,3 +34,84 @@ simTransporte : Es un box por donde las botellas pueden desplazarse usando un tr
* Se ha reemplazado el sistema `ProcessPressureSystem` por un nuevo sistema de calibración visual `HighSlippery` que permite ajustar las fricciones de cada componente. Cada botella tiene una propiedad `HighSlippery` (rango 0-9) con sistema de heatup/cooldown que se incrementa durante deslizamiento y se decrementa durante adherencia. El valor se muestra como número en el centro de cada botella para calibración visual. Los `SpringSettings` se mantienen >= 30 para evitar compresiones irreales. El sistema permite calibrar las tasas de heatup/cooldown específicas para transportes (0.2/0.1) y curvas (0.15/0.08), y el umbral de cambio estático/dinámico (HighSlippery > 3). Este sistema elimina el problema del cooldown que reactivaba prematuramente la fricción estática. * Se ha reemplazado el sistema `ProcessPressureSystem` por un nuevo sistema de calibración visual `HighSlippery` que permite ajustar las fricciones de cada componente. Cada botella tiene una propiedad `HighSlippery` (rango 0-9) con sistema de heatup/cooldown que se incrementa durante deslizamiento y se decrementa durante adherencia. El valor se muestra como número en el centro de cada botella para calibración visual. Los `SpringSettings` se mantienen >= 30 para evitar compresiones irreales. El sistema permite calibrar las tasas de heatup/cooldown específicas para transportes (0.2/0.1) y curvas (0.15/0.08), y el umbral de cambio estático/dinámico (HighSlippery > 3). Este sistema elimina el problema del cooldown que reactivaba prematuramente la fricción estática.
# Sistema de Limitación de Fuerzas para Contactos Múltiples Simultáneos
## Problema Identificado
Durante la simulación de "trenes largos" de botellas que impactan simultáneamente, se generaban fuerzas armónicas irreales que no ocurren en la vida real. Aunque tengas 50 botellas empujando, la fuerza máxima está limitada por la resistencia de los materiales.
## Solución Implementada
### 1. Sistema de Tracking de Densidad de Contactos
- **Discretización espacial**: El espacio se divide en regiones de 0.5m x 0.5m x 0.5m
- **Contador por región**: Se cuenta el número de contactos simultáneos en cada región
- **Límite de contactos**: Máximo 10 contactos antes de aplicar limitación de fuerzas
### 2. Factor de Escala Dinámico
```csharp
// Sin limitación hasta 10 contactos
if (contactCount <= 10) return 1.0f;
// Reducción progresiva: 5% por cada contacto extra
var scaleFactor = 1.0f - (excessContacts * 0.05f);
return Math.Max(scaleFactor, 0.3f); // Mínimo 30% de fuerza original
```
### 3. Aplicación Integral
- **SpringSettings**: Se escalan proporcionalmente (frecuencia reducida)
- **MaximumRecoveryVelocity**: Se reduce proporcionalmente
- **Todos los tipos de contacto**: Botella-botella, botella-transporte, botella-curva, etc.
## Beneficios
- **Comportamiento realista**: Elimina fuerzas armónicas irreales en impactos múltiples
- **Estabilidad mejorada**: Previene explosiones por acumulación de fuerzas
- **Configurabilidad**: Límites y factores ajustables según necesidades
- **Mantenimiento de rigidez**: Las fuerzas normales permanecen intactas hasta el límite
# Sistema de Intervención Post-Solver para Control de Velocidades Z
## Problema Identificado
El sistema anterior de limitación de fuerzas pre-solver no controlaba directamente las velocidades Z excesivas generadas por el solver de BEPU. Las botellas seguían elevándose debido a fuerzas de separación acumuladas que se aplicaban después de las restricciones iniciales.
## Solución Implementada
### 1. Enfoque Post-Solver como "Fusible de Energía"
- **Timing crítico**: Intervención en `OnSubstepEnded` - después del solver, antes de la integración de posición
- **Fusible de energía**: Elimina velocidades Z excesivas sin interferir con el comportamiento normal del solver
- **Preservación de física**: Permite al solver trabajar con fuerzas realistas, interviniendo solo cuando es necesario
### 2. Control de Velocidades Z Granular
```csharp
// Límites configurables
const float maxUpwardVelocity = 0.15f; // Anti-elevación
const float maxDownwardVelocity = 2.0f; // Anti-hundimiento
const float zDampingFactor = 0.7f; // Amortiguamiento suave
// Intervención directa en velocidades
if (velocity.Linear.Z > maxUpwardVelocity) {
velocity.Linear.Z = maxUpwardVelocity;
}
```
### 3. Corrección de Posición Extrema
- **Detección de altura excesiva**: Si la botella está muy por encima del nivel normal
- **Corrección suave**: Aplicar velocidad correctiva hacia abajo (máximo 0.5 m/s)
- **Cálculo proporcional**: Velocidad correctiva basada en la altura excesiva
## Simplificación del Sistema
### 1. Eliminación de `ApplyZForceLimitation`
- **Antes**: Limitación pre-solver que interfería con el comportamiento normal
- **Ahora**: Intervención post-solver más directa y efectiva
### 2. Simplificación de `IntegrateVelocity`
- **Antes**: Control complejo de velocidades Z durante la integración
- **Ahora**: Solo control angular básico (anti-vuelco) y damping en Z
- **Beneficio**: Menor interferencia con la física normal de BEPU
## Beneficios del Enfoque Post-Solver
- **Más efectivo**: Intervención después de que el solver haya calculado todas las fuerzas
- **Menos interferencia**: Permite comportamiento físico normal hasta el punto de intervención
- **Más directo**: Control directo de velocidades en lugar de modificar parámetros de material
- **Más predecible**: Comportamiento determinista independiente de las configuraciones del solver
- **Mantenimiento de rigidez**: Las fuerzas normales en XY permanecen intactas

View File

@ -195,7 +195,7 @@ namespace CtrEditor.ObjetosSim
Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa Inercia_desde_simulacion = SimGeometria.Mass; // En BEPU usamos masa en lugar de inercia directa
Porcentaje_Traccion = SimGeometria.OverlapPercentage; Porcentaje_Traccion = SimGeometria.OverlapPercentage;
EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno EnTransporteConFreno = SimGeometria.isOnBrakeTransport; // Actualizar estado de freno
NivelDeslizamiento = SimGeometria.HighSlippery; // ✅ NUEVO: Mostrar nivel de deslizamiento para calibración NivelDeslizamiento = 10-SimGeometria.forceScale*10; // ✅ NUEVO: Mostrar nivel de deslizamiento para calibración
} }
public override void ucLoaded() public override void ucLoaded()

View File

@ -95,6 +95,28 @@ namespace CtrEditor.Simulacion
SpringSettings = new SpringSettings(120, 8) // ✅ AJUSTADO: Más rígido pero bien amortiguado SpringSettings = new SpringSettings(120, 8) // ✅ AJUSTADO: Más rígido pero bien amortiguado
}; };
// ✅ NUEVO: SISTEMA DE LIMITACIÓN DE FUERZAS PARA CONTACTOS MÚLTIPLES
float forceScale = 1.0f;
if (_simulationManager != null)
{
var contactPosition = _simulationManager.GetContactPosition(pair);
forceScale = _simulationManager.RegisterContactAndGetForceScale(contactPosition);
// Aplicar limitación de fuerzas si hay demasiados contactos simultáneos
if (forceScale < 1.0f)
{
// Reducir SpringSettings proporcionalmente
var originalFreq = pairMaterial.SpringSettings.Frequency;
var originalDamp = pairMaterial.SpringSettings.DampingRatio;
pairMaterial.SpringSettings = new SpringSettings(originalFreq * forceScale, originalDamp);
// Reducir velocidad de recuperación proporcionalmente
pairMaterial.MaximumRecoveryVelocity *= forceScale;
//System.Diagnostics.Debug.WriteLine($"[ConfigureContactManifold] ⚠️ LIMITANDO FUERZAS: Factor={forceScale:F2}, Región={contactPosition}");
}
}
if (_simulationManager != null) if (_simulationManager != null)
{ {
var botellaA = GetBotellaFromCollidable(pair.A); var botellaA = GetBotellaFromCollidable(pair.A);
@ -124,6 +146,7 @@ namespace CtrEditor.Simulacion
// ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA // ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
if (botella != null && (transportA != null || transportB != null)) if (botella != null && (transportA != null || transportB != null))
{ {
botella.forceScale = forceScale;
// ✅ NUEVO: Lógica de control de presión/flotación // ✅ NUEVO: Lógica de control de presión/flotación
botella.IsTouchingTransport = true; botella.IsTouchingTransport = true;
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B; var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
@ -164,23 +187,24 @@ namespace CtrEditor.Simulacion
} }
botella.isOnBrakeTransport = true; botella.isOnBrakeTransport = true;
pairMaterial.FrictionCoefficient = 2f; pairMaterial.FrictionCoefficient = 8f;
pairMaterial.MaximumRecoveryVelocity = 0.5f; // ✅ AJUSTADO: Velocidad de separación controlada pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(80, 6f); // ✅ AJUSTADO: Rígido para frenado pairMaterial.SpringSettings = new SpringSettings(80 * forceScale, 6f); // ✅ APLICAR FACTOR DE ESCALA
} }
else else
{ {
botella.isOnBrakeTransport = false; botella.isOnBrakeTransport = false;
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento // ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
pairMaterial.FrictionCoefficient = CalculateTransportFriction(botella, transport, botellaCollidable); pairMaterial.FrictionCoefficient = CalculateTransportFriction(botella, transport, botellaCollidable);
pairMaterial.MaximumRecoveryVelocity = 0.6f; // ✅ AJUSTADO: Velocidad de separación más natural pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(100, 8f); // ✅ AJUSTADO: Rígido para transportes pairMaterial.SpringSettings = new SpringSettings(100 * forceScale, 8f); // ✅ APLICAR FACTOR DE ESCALA
} }
} }
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA // ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
else if (botella != null && (curveA != null || curveB != null)) else if (botella != null && (curveA != null || curveB != null))
{ {
botella.forceScale = forceScale;
// ✅ NUEVO: Lógica de control de presión/flotación // ✅ NUEVO: Lógica de control de presión/flotación
botella.IsTouchingTransport = true; botella.IsTouchingTransport = true;
botella.isOnBrakeTransport = false; botella.isOnBrakeTransport = false;
@ -194,16 +218,16 @@ namespace CtrEditor.Simulacion
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento // ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
var curve = curveA ?? curveB; var curve = curveA ?? curveB;
pairMaterial.FrictionCoefficient = CalculateCurveFriction(botella, curve, botellaCollidable); pairMaterial.FrictionCoefficient = CalculateCurveFriction(botella, curve, botellaCollidable);
pairMaterial.MaximumRecoveryVelocity = 0.6f; // ✅ AJUSTADO: Velocidad de separación más natural pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(100, 8f); // ✅ AJUSTADO: Rígido para curvas pairMaterial.SpringSettings = new SpringSettings(100 * forceScale, 8f); // ✅ APLICAR FACTOR DE ESCALA
} }
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción // ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null)) else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
{ {
// Fricción baja para las guías, deben deslizar, no arrastrar. // Fricción baja para las guías, deben deslizar, no arrastrar.
pairMaterial.FrictionCoefficient = 0.01f; pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 0.5f; // ✅ AJUSTADO: Velocidad de separación normal pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(120, 10f); // ✅ AJUSTADO: Rígido para guías pairMaterial.SpringSettings = new SpringSettings(120 * forceScale, 10f); // ✅ APLICAR FACTOR DE ESCALA
} }
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Lógica de presión proactiva basada en la penetración. // ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Lógica de presión proactiva basada en la penetración.
else if (botellaA != null && botellaB != null) else if (botellaA != null && botellaB != null)
@ -217,15 +241,15 @@ namespace CtrEditor.Simulacion
{ {
// Fuerzas controladas para penetraciones profundas - evitar explosiones pero mantener rigidez // Fuerzas controladas para penetraciones profundas - evitar explosiones pero mantener rigidez
pairMaterial.FrictionCoefficient = 0.01f; pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 0.3f; // ✅ AUMENTADO: Más velocidad de separación pairMaterial.MaximumRecoveryVelocity = 0.3f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(60, 12f); // ✅ AUMENTADO: Más rígido pero bien amortiguado pairMaterial.SpringSettings = new SpringSettings(60 * forceScale, 12f); // ✅ APLICAR FACTOR DE ESCALA
} }
else else
{ {
// Fuerzas normales para contactos superficiales - botellas rígidas // Fuerzas normales para contactos superficiales - botellas rígidas
pairMaterial.FrictionCoefficient = 0.02f; pairMaterial.FrictionCoefficient = 0.02f;
pairMaterial.MaximumRecoveryVelocity = 0.5f; // ✅ AUMENTADO: Velocidad normal de separación pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(150, 10f); // ✅ AUMENTADO: Muy rígido para contactos normales pairMaterial.SpringSettings = new SpringSettings(150 * forceScale, 10f); // ✅ APLICAR FACTOR DE ESCALA
} }
} }
// Ajustes básicos para otras botellas // Ajustes básicos para otras botellas
@ -234,8 +258,8 @@ namespace CtrEditor.Simulacion
System.Diagnostics.Debug.WriteLine($"[ConfigureContactManifold] ERROR: contacto no resuelto!"); System.Diagnostics.Debug.WriteLine($"[ConfigureContactManifold] ERROR: contacto no resuelto!");
// Fricción moderada para colisiones entre botellas. // Fricción moderada para colisiones entre botellas.
pairMaterial.FrictionCoefficient = 0.3f; pairMaterial.FrictionCoefficient = 0.3f;
pairMaterial.MaximumRecoveryVelocity = 0.6f; // ✅ AJUSTADO: Velocidad de separación normal pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(120, 8); // ✅ AJUSTADO: Rígido pero controlado pairMaterial.SpringSettings = new SpringSettings(120 * forceScale, 8); // ✅ APLICAR FACTOR DE ESCALA
} }
} }
@ -479,6 +503,8 @@ namespace CtrEditor.Simulacion
} }
} }
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold) public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
{ {
return true; return true;
@ -522,7 +548,8 @@ namespace CtrEditor.Simulacion
{ {
} }
// ✅ REESCRITO COMPLETAMENTE: para corregir errores de compilación y aplicar la lógica de amortiguamiento de forma segura. // ✅ SIMPLIFICADO: IntegrateVelocity ahora solo aplica física básica
// El control de velocidades Z se maneja en OnSubstepEnded (post-solver)
public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity) public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
{ {
// 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO) // 1. APLICAR GRAVEDAD Y AMORTIGUAMIENTO ESTÁNDAR (VECTORIZADO)
@ -537,7 +564,7 @@ namespace CtrEditor.Simulacion
velocity.Linear *= linearDampingFactor; velocity.Linear *= linearDampingFactor;
velocity.Angular *= angularDampingFactor; velocity.Angular *= angularDampingFactor;
// 2. LÓGICA PERSONALIZADA PARA BOTELLAS (ESCALAR) // 2. LÓGICA PERSONALIZADA MÍNIMA PARA BOTELLAS (ESCALAR)
const float bottleExtraZAngularDamping = 0.8f; const float bottleExtraZAngularDamping = 0.8f;
var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]); var bottleZAngularDampingFactor = MathF.Pow(MathHelper.Clamp(1 - bottleExtraZAngularDamping, 0, 1), dt[0]);
@ -553,51 +580,24 @@ namespace CtrEditor.Simulacion
var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex]; var handle = _simulationManager.simulation.Bodies.ActiveSet.IndexToHandle[bodyIndex];
if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella) if (_simulationManager.CollidableData.TryGetValue(handle.Value, out simBase baseObj) && baseObj is simBotella)
{ {
//// B. DAMPING ANGULAR EXTRA EN EJE Z // EXTRAER VELOCIDADES DEL PAQUETE A ARRAYS
//// 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);
// --- INICIO DE NUESTRA LÓGICA ANTI-SALTO Y ANTI-VUELCO ---
// 1. EXTRAER VELOCIDADES DEL PAQUETE A ARRAYS
var linearX = new float[Vector<float>.Count];
var linearY = new float[Vector<float>.Count];
var linearZ = new float[Vector<float>.Count];
var angularX = new float[Vector<float>.Count]; var angularX = new float[Vector<float>.Count];
var angularY = new float[Vector<float>.Count]; var angularY = new float[Vector<float>.Count];
var angularZ = new float[Vector<float>.Count]; var angularZ = new float[Vector<float>.Count];
velocity.Linear.X.CopyTo(linearX, 0);
velocity.Linear.Y.CopyTo(linearY, 0);
velocity.Linear.Z.CopyTo(linearZ, 0);
velocity.Angular.X.CopyTo(angularX, 0); velocity.Angular.X.CopyTo(angularX, 0);
velocity.Angular.Y.CopyTo(angularY, 0); velocity.Angular.Y.CopyTo(angularY, 0);
velocity.Angular.Z.CopyTo(angularZ, 0); velocity.Angular.Z.CopyTo(angularZ, 0);
// 2. APLICAR RESTRICCIONES AL CARRIL 'i' (la botella) // CONTROL ANGULAR BÁSICO PARA BOTELLAS
// Anti-vuelco: Anular rotaciones que tumben la botella
// ANTI-VUELCO: Anular cualquier velocidad angular que tumbe la botella.
angularX[i] = 0; angularX[i] = 0;
angularY[i] = 0; angularY[i] = 0;
// ANTI-SALTO: Si la velocidad Z es positiva (hacia arriba), anularla. // Damping angular extra en Z
// Esto permite que la gravedad actúe (Z negativo) pero impide saltos.
if (linearZ[i] > 0)
{
linearZ[i] = 0;
}
// DAMPING ANGULAR EXTRA EN EJE Z (Tu lógica original)
angularZ[i] *= bottleZAngularDampingFactor; angularZ[i] *= bottleZAngularDampingFactor;
// RECONSTRUIR LOS VECTORES 'WIDE' CON LOS VALORES MODIFICADOS
// 3. RECONSTRUIR LOS VECTORES 'WIDE' CON LOS VALORES MODIFICADOS
velocity.Linear.X = new Vector<float>(linearX);
velocity.Linear.Y = new Vector<float>(linearY);
velocity.Linear.Z = new Vector<float>(linearZ);
velocity.Angular.X = new Vector<float>(angularX); velocity.Angular.X = new Vector<float>(angularX);
velocity.Angular.Y = new Vector<float>(angularY); velocity.Angular.Y = new Vector<float>(angularY);
velocity.Angular.Z = new Vector<float>(angularZ); velocity.Angular.Z = new Vector<float>(angularZ);
@ -654,6 +654,12 @@ namespace CtrEditor.Simulacion
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>(); public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
public float GlobalTime { get; private set; } public float GlobalTime { get; private set; }
// ✅ NUEVO: Sistema de limitación de fuerzas para contactos múltiples simultáneos
private Dictionary<Vector3, int> _contactDensityMap = new Dictionary<Vector3, int>();
private const int MAX_SIMULTANEOUS_CONTACTS = 10; // Máximo número de contactos antes de aplicar clamp
private const float CONTACT_REGION_SIZE = 0.5f; // Tamaño de región para agrupar contactos (metros)
private const float MAX_FORCE_SCALE_FACTOR = 0.3f; // Factor mínimo de escala cuando hay muchos contactos
/// <summary> /// <summary>
/// Obtiene el objeto simBase correspondiente a un BodyHandle /// Obtiene el objeto simBase correspondiente a un BodyHandle
/// </summary> /// </summary>
@ -831,10 +837,56 @@ namespace CtrEditor.Simulacion
/// <summary> /// <summary>
/// ✅ NUEVO: Se ejecuta al final de cada sub-paso de la simulación, pero antes de la integración de la pose. /// ✅ 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. /// Actúa como "fusible de energía" para eliminar velocidades Z excesivas y mantener objetos cinemáticos estáticos.
/// </summary> /// </summary>
private void OnSubstepEnded(int substepIndex) private void OnSubstepEnded(int substepIndex)
{ {
// 1. INTERVENCIÓN POST-SOLVER: Control de velocidades Z para botellas
// Esto actúa como un "fusible de energía" que elimina velocidades Z excesivas
// después de que el solver haya hecho su trabajo
foreach (var botella in Cuerpos.OfType<simBotella>())
{
try
{
if (botella?.BodyHandle.Value >= 0 && simulation?.Bodies?.BodyExists(botella.BodyHandle) == true)
{
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
var velocity = body.Velocity;
// CONFIGURACIÓN DE LÍMITES
const float maxUpwardVelocity = 0f; // Velocidad Z máxima hacia arriba (m/s)
// LIMITAR VELOCIDADES Z EXCESIVAS
if (velocity.Linear.Z > maxUpwardVelocity)
{
// Cancelar velocidades hacia arriba excesivas (anti-elevación)
velocity.Linear.Z = maxUpwardVelocity;
body.Velocity = velocity;
}
// CONTROL DE POSICIÓN EXTREMA
// Si la botella está muy por encima del nivel normal, aplicar corrección
var position = body.Pose.Position;
var maxAllowedZ = simBase.zPos_Transporte + simBase.zAltura_Transporte + botella.Radius * 2;
if (position.Z > maxAllowedZ)
{
// Aplicar velocidad correctiva hacia abajo
var excessHeight = position.Z - maxAllowedZ;
var correctionVelocity = -Math.Min(excessHeight * 5f, 0.5f); // Máximo 0.5 m/s hacia abajo
velocity.Linear.Z = correctionVelocity;
body.Velocity = velocity;
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[OnSubstepEnded] Error procesando botella {botella?.BodyHandle}: {ex.Message}");
}
}
// 2. LIMPIAR VELOCIDADES DE OBJETOS CINEMÁTICOS
// Mantener transportes y curvas estáticos después del solver
foreach (var transport in Cuerpos.OfType<simTransporte>()) foreach (var transport in Cuerpos.OfType<simTransporte>())
{ {
if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle)) if (transport.BodyHandle.Value >= 0 && simulation.Bodies.BodyExists(transport.BodyHandle))
@ -977,8 +1029,13 @@ namespace CtrEditor.Simulacion
botella.ContactPressure = 0f; botella.ContactPressure = 0f;
} }
// ✅ NUEVO: APLICAR LIMITACIÓN DE FUERZAS Z ANTES DEL TIMESTEP // ✅ NUEVO: RESETEAR MAPA DE DENSIDAD DE CONTACTOS
ApplyZForceLimitation(); lock (_contactsLock)
{
_contactDensityMap.Clear();
}
// ✅ ELIMINADO: ApplyZForceLimitation - ahora usamos intervención post-solver en OnSubstepEnded
// ✅ CONSERVAR - validación de deltaTime // ✅ CONSERVAR - validación de deltaTime
var currentTime = stopwatch.Elapsed.TotalMilliseconds; var currentTime = stopwatch.Elapsed.TotalMilliseconds;
@ -1541,69 +1598,97 @@ namespace CtrEditor.Simulacion
} }
/// <summary> /// <summary>
/// ✅ NUEVA FUNCIÓN: Limita las fuerzas Z para evitar que las botellas se eleven /// ✅ NUEVA FUNCIÓN: Discretiza una posición en regiones para el tracking de densidad de contactos
/// Esta función se ejecuta ANTES del timestep para controlar fuerzas de separación excesivas
/// </summary> /// </summary>
private void ApplyZForceLimitation() private Vector3 GetContactRegion(Vector3 position)
{
// Discretizar la posición en regiones de tamaño CONTACT_REGION_SIZE
return new Vector3(
MathF.Floor(position.X / CONTACT_REGION_SIZE) * CONTACT_REGION_SIZE,
MathF.Floor(position.Y / CONTACT_REGION_SIZE) * CONTACT_REGION_SIZE,
MathF.Floor(position.Z / CONTACT_REGION_SIZE) * CONTACT_REGION_SIZE
);
}
/// <summary>
/// ✅ NUEVA FUNCIÓN: Registra un contacto en el mapa de densidad y retorna el factor de escala de fuerzas
/// </summary>
public float RegisterContactAndGetForceScale(Vector3 contactPosition)
{ {
try try
{ {
const float maxZVelocity = 0.3f; // ✅ AJUSTADO: Velocidad Z máxima permitida más realista (m/s) var region = GetContactRegion(contactPosition);
const float zVelocityDamping = 0.8f; // ✅ AJUSTADO: Amortiguamiento menos agresivo para Z
foreach (var botella in Cuerpos.OfType<simBotella>()) lock (_contactsLock)
{ {
try // Incrementar contador de contactos en esta región
if (_contactDensityMap.ContainsKey(region))
{ {
// Verificar que la botella aún existe en la simulación _contactDensityMap[region]++;
if (botella?.BodyHandle.Value >= 0 && simulation?.Bodies?.BodyExists(botella.BodyHandle) == true)
{
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
var velocity = body.Velocity;
// ✅ LIMITACIÓN DIRECTA DE VELOCIDAD Z
if (velocity.Linear.Z > maxZVelocity)
{
velocity.Linear.Z = maxZVelocity;
body.Velocity = velocity;
}
// ✅ AMORTIGUAMIENTO ADICIONAL EN Z
if (velocity.Linear.Z > 0)
{
velocity.Linear.Z *= zVelocityDamping;
body.Velocity = velocity;
}
// ✅ LIMITACIÓN DE FUERZAS EXTREMAS POR PENETRACIÓN
// Si la botella está muy por encima del nivel normal, aplicar fuerza hacia abajo
var position = body.Pose.Position;
var maxAllowedZ = simBase.zPos_Transporte + simBase.zAltura_Transporte + botella.Radius * 2;
if (position.Z > maxAllowedZ)
{
// Aplicar fuerza hacia abajo proporcional a la elevación excesiva
var excessHeight = position.Z - maxAllowedZ;
var downwardForce = new Vector3(0, 0, -excessHeight * 20f); // ✅ AJUSTADO: Fuerza menos agresiva
// Aplicar impulso para corregir la posición
velocity.Linear += downwardForce * (1f / 60f); // Asumir 60 FPS
body.Velocity = velocity;
}
}
} }
catch (Exception ex) else
{ {
System.Diagnostics.Debug.WriteLine($"[ApplyZForceLimitation] Error procesando botella {botella?.BodyHandle}: {ex.Message}"); _contactDensityMap[region] = 1;
}
var contactCount = _contactDensityMap[region];
// Calcular factor de escala basado en número de contactos
if (contactCount <= MAX_SIMULTANEOUS_CONTACTS)
{
return 1.0f; // Sin limitación
}
else
{
// Aplicar factor de escala que decrece con más contactos
// Factor va desde 1.0 (10 contactos) hasta MAX_FORCE_SCALE_FACTOR (muchos contactos)
var excessContacts = contactCount - MAX_SIMULTANEOUS_CONTACTS;
var scaleFactor = 1.0f - (excessContacts * 0.05f); // Reducir 5% por cada contacto extra
return Math.Max(scaleFactor, MAX_FORCE_SCALE_FACTOR);
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"[ApplyZForceLimitation] Error crítico: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"[RegisterContactAndGetForceScale] Error: {ex.Message}");
return 1.0f; // Sin limitación en caso de error
} }
} }
/// <summary>
/// ✅ NUEVA FUNCIÓN: Calcula la posición promedio de contacto entre dos collidables
/// </summary>
public Vector3 GetContactPosition(CollidablePair pair)
{
try
{
Vector3 positionA = Vector3.Zero;
Vector3 positionB = Vector3.Zero;
// Obtener posición del primer objeto
if (pair.A.BodyHandle.Value >= 0 && simulation?.Bodies?.BodyExists(pair.A.BodyHandle) == true)
{
positionA = simulation.Bodies[pair.A.BodyHandle].Pose.Position;
}
// Obtener posición del segundo objeto
if (pair.B.BodyHandle.Value >= 0 && simulation?.Bodies?.BodyExists(pair.B.BodyHandle) == true)
{
positionB = simulation.Bodies[pair.B.BodyHandle].Pose.Position;
}
// Retornar punto medio
return (positionA + positionB) * 0.5f;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[GetContactPosition] Error: {ex.Message}");
return Vector3.Zero;
}
}
// ✅ ELIMINADO: ApplyZForceLimitation - reemplazado por intervención post-solver en OnSubstepEnded
public void Dispose() public void Dispose()
{ {
Clear(); Clear();

View File

@ -38,6 +38,7 @@ namespace CtrEditor.Simulacion
// ✅ NUEVO: Sistema de fricción dinámica con cooldown/heatup // ✅ NUEVO: Sistema de fricción dinámica con cooldown/heatup
public float HighSlippery { get; set; } = 0f; // Rango 0-9, donde 9 es máxima presión/deslizamiento public float HighSlippery { get; set; } = 0f; // Rango 0-9, donde 9 es máxima presión/deslizamiento
public float ContactPressure { get; set; } = 0f; // Rango 0-9, donde 9 es mínima presión/deslizamiento public float ContactPressure { get; set; } = 0f; // Rango 0-9, donde 9 es mínima presión/deslizamiento
public float forceScale { get; set; } = 1f;
// ✅ NUEVO: Propiedades para la física personalizada // ✅ NUEVO: Propiedades para la física personalizada
public float BaseInverseMass { get; private set; } public float BaseInverseMass { get; private set; }