Se eliminó el sistema de limitación de fuerzas para contactos múltiples, reemplazándolo por un enfoque de intervención post-solver que mejora el control de velocidades Z y simplifica la lógica de simulación. Se ajustaron los parámetros de fricción y recuperación, optimizando el comportamiento de las botellas en situaciones de contacto. Además, se eliminaron funciones relacionadas con el seguimiento de densidad de contactos, simplificando el código y mejorando la eficiencia del sistema.

This commit is contained in:
Miguel 2025-07-09 12:05:49 +02:00
parent b44bdc5ece
commit 18017db56a
2 changed files with 76 additions and 186 deletions

View File

@ -34,16 +34,16 @@ 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.
# Sistema de Limitación de Fuerzas para Contactos Múltiples Simultáneos
# Sistema de Limitación de Fuerzas para Contactos Múltiples Simultáneos (ELIMINADO)
## 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
## Solución Inicial Implementada (OBSOLETA)
### 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
- **Discretización espacial**: El espacio se dividía en regiones de 0.5m x 0.5m x 0.5m
- **Contador por región**: Se contaba 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
@ -57,20 +57,21 @@ 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
- **SpringSettings**: Se escalaban proporcionalmente (frecuencia reducida)
- **MaximumRecoveryVelocity**: Se reducía 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
## Razón para la Eliminación
Este sistema se **eliminó completamente** porque:
- Era un enfoque indirecto modificando parámetros de material
- El nuevo sistema de **intervención post-solver** es más efectivo
- La cancelación directa de velocidades Z es más elegante y simple
- Reducía la complejidad del código sin comprometer la funcionalidad
# 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.
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. Además, el sistema de `forceScale` para contactos múltiples era un enfoque indirecto que no atacaba la causa raíz del problema.
## Solución Implementada
@ -114,4 +115,6 @@ if (velocity.Linear.Z > maxUpwardVelocity) {
- **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
- **Solución integral**: Resuelve tanto el problema de elevación como el de contactos múltiples simultáneos
- **Código más simple**: Elimina la complejidad del sistema de tracking de densidad de contactos

View File

@ -95,27 +95,7 @@ namespace CtrEditor.Simulacion
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}");
}
}
// ✅ ELIMINADO: Sistema de limitación de fuerzas forceScale - reemplazado por intervención post-solver
if (_simulationManager != null)
{
@ -146,7 +126,7 @@ namespace CtrEditor.Simulacion
// ✅ CONTACTO BOTELLA-TRANSPORTE: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
if (botella != null && (transportA != null || transportB != null))
{
botella.forceScale = forceScale;
// ✅ ELIMINADO: botella.forceScale = forceScale;
// ✅ NUEVO: Lógica de control de presión/flotación
botella.IsTouchingTransport = true;
var botellaCollidable = GetBotellaFromCollidable(pair.A) != null ? pair.A : pair.B;
@ -188,23 +168,23 @@ namespace CtrEditor.Simulacion
botella.isOnBrakeTransport = true;
pairMaterial.FrictionCoefficient = 8f;
pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(80 * forceScale, 6f); // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
}
else
{
botella.isOnBrakeTransport = false;
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
pairMaterial.FrictionCoefficient = CalculateTransportFriction(botella, transport, botellaCollidable);
pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(100 * forceScale, 8f); // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
}
}
// ✅ CONTACTO BOTELLA-CURVA: USAR FRICCIÓN Y VELOCIDAD CINEMÁTICA
else if (botella != null && (curveA != null || curveB != null))
{
botella.forceScale = forceScale;
// ✅ ELIMINADO: botella.forceScale = forceScale;
// ✅ NUEVO: Lógica de control de presión/flotación
botella.IsTouchingTransport = true;
botella.isOnBrakeTransport = false;
@ -218,16 +198,16 @@ namespace CtrEditor.Simulacion
// ✅ NUEVO: Usar fricción dinámica basada en deslizamiento
var curve = curveA ?? curveB;
pairMaterial.FrictionCoefficient = CalculateCurveFriction(botella, curve, botellaCollidable);
pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(100 * forceScale, 8f); // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(30, 1f); // ✅ REMOVIDO: * forceScale
}
// ✅ CONTACTO BOTELLA-GUÍA: Configuración específica de fricción
else if (botella != null && (GetGuiaFromCollidable(pair.A) != null || GetGuiaFromCollidable(pair.B) != null))
{
// Fricción baja para las guías, deben deslizar, no arrastrar.
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(120 * forceScale, 10f); // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.MaximumRecoveryVelocity = 0.95f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(40, 1f); // ✅ REMOVIDO: * forceScale
}
// ✅ NUEVO: CONTACTO BOTELLA-BOTELLA: Lógica de presión proactiva basada en la penetración.
else if (botellaA != null && botellaB != null)
@ -236,30 +216,20 @@ namespace CtrEditor.Simulacion
CalculateBottlePression(ref manifold, botellaA, botellaB);
// ✅ NUEVO: Limitar fuerzas basadas en profundidad de penetración
var maxPenetrationDepth = GetMaxPenetrationDepth(ref manifold);
if (maxPenetrationDepth > 0.005f) // Penetración profunda (reducido umbral)
{
// Fuerzas controladas para penetraciones profundas - evitar explosiones pero mantener rigidez
pairMaterial.FrictionCoefficient = 0.01f;
pairMaterial.MaximumRecoveryVelocity = 0.3f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(60 * forceScale, 12f); // ✅ APLICAR FACTOR DE ESCALA
}
else
{
// Fuerzas normales para contactos superficiales - botellas rígidas
pairMaterial.FrictionCoefficient = 0.02f;
pairMaterial.MaximumRecoveryVelocity = 0.5f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(150 * forceScale, 10f); // ✅ APLICAR FACTOR DE ESCALA
}
//var maxPenetrationDepth = GetMaxPenetrationDepth(ref manifold);
// Fuerzas normales para contactos superficiales - botellas rígidas
pairMaterial.FrictionCoefficient = 0.02f;
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(40, 10f); // ✅ REMOVIDO: * forceScale
}
// Ajustes básicos para otras botellas
else if (botella != null)
{
System.Diagnostics.Debug.WriteLine($"[ConfigureContactManifold] ERROR: contacto no resuelto!");
// Fricción moderada para colisiones entre botellas.
pairMaterial.FrictionCoefficient = 0.3f;
pairMaterial.MaximumRecoveryVelocity = 0.6f * forceScale; // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.SpringSettings = new SpringSettings(120 * forceScale, 8); // ✅ APLICAR FACTOR DE ESCALA
pairMaterial.FrictionCoefficient = 0f;
pairMaterial.MaximumRecoveryVelocity = 1f; // ✅ REMOVIDO: * forceScale
pairMaterial.SpringSettings = new SpringSettings(80, 1); // ✅ REMOVIDO: * forceScale
}
}
@ -369,9 +339,9 @@ namespace CtrEditor.Simulacion
float slipSpeedThreshold = transportVelocity.Length()*0.85f; // m/s - umbral para cambiar de fricción estática a dinámica
const float heatupRate = 1f; // Qué tan rápido sube HighSlippery
const float cooldownRate = 0.2f; // Qué tan rápido baja HighSlippery
const float staticFriction = 0.30f;
const float dynamicFriction = 0.10f;
const float cooldownRate = 1f; // Qué tan rápido baja HighSlippery
const float staticFriction = 0.80f;
const float dynamicFriction = 0.50f;
// Sistema de heatup/cooldown para HighSlippery
if (slipSpeed > slipSpeedThreshold) // && botella.ContactPressure > 0
@ -381,10 +351,15 @@ namespace CtrEditor.Simulacion
// Calcular fricción basada en HighSlippery
// HighSlippery > 3 = fricción dinámica, <= 3 = fricción estática
if (botella.HighSlippery > 3f)
return dynamicFriction;
if (botella.ContactPressure > 0)
return dynamicFriction / 10;
else
return staticFriction;
{
if (botella.HighSlippery > 1f)
return dynamicFriction;
else
return staticFriction;
}
}
}
catch (Exception ex)
@ -420,9 +395,9 @@ namespace CtrEditor.Simulacion
float slipSpeedThreshold = curveVelocityAtPoint.Length() * 0.85f; // m/s
const float heatupRate = 1f; // Qué tan rápido sube HighSlippery en curvas
const float cooldownRate = 0.2f; // Qué tan rápido baja HighSlippery en curvas
const float staticFriction = 0.30f;
const float dynamicFriction = 0.10f;
const float cooldownRate = 1f; // Qué tan rápido baja HighSlippery en curvas
const float staticFriction = 0.80f;
const float dynamicFriction = 0.50f;
// Sistema de heatup/cooldown para HighSlippery
if (slipSpeed > slipSpeedThreshold) // && botella.ContactPressure > 0
@ -432,10 +407,15 @@ namespace CtrEditor.Simulacion
// Calcular fricción basada en HighSlippery
// HighSlippery > 3 = fricción dinámica, <= 3 = fricción estática
if (botella.HighSlippery > 3f)
return dynamicFriction;
if (botella.ContactPressure > 0)
return dynamicFriction / 10;
else
return staticFriction;
{
if (botella.HighSlippery > 3f)
return dynamicFriction;
else
return staticFriction;
}
}
}
catch (Exception ex)
@ -654,11 +634,7 @@ namespace CtrEditor.Simulacion
public Dictionary<int, simBase> CollidableData = new Dictionary<int, simBase>();
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
// ✅ ELIMINADO: Sistema de limitación de fuerzas - reemplazado por intervención post-solver
/// <summary>
/// Obtiene el objeto simBase correspondiente a un BodyHandle
@ -852,7 +828,8 @@ namespace CtrEditor.Simulacion
{
var body = simulation.Bodies.GetBodyReference(botella.BodyHandle);
var velocity = body.Velocity;
var position = body.Pose.Position;
// CONFIGURACIÓN DE LÍMITES
const float maxUpwardVelocity = 0f; // Velocidad Z máxima hacia arriba (m/s)
@ -860,23 +837,24 @@ namespace CtrEditor.Simulacion
if (velocity.Linear.Z > maxUpwardVelocity)
{
// Cancelar velocidades hacia arriba excesivas (anti-elevación)
velocity.Linear.Z = maxUpwardVelocity;
velocity.Linear.Z = 0;
position.Z = botella.Radius + simBase.zPos_Transporte + simBase.zAltura_Transporte;
body.Velocity = velocity;
body.Pose = position;
}
// 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;
//// CONTROL DE POSICIÓN EXTREMA
//// Si la botella está muy por encima del nivel normal, aplicar corrección
//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;
}
//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)
@ -1029,11 +1007,7 @@ namespace CtrEditor.Simulacion
botella.ContactPressure = 0f;
}
// ✅ NUEVO: RESETEAR MAPA DE DENSIDAD DE CONTACTOS
lock (_contactsLock)
{
_contactDensityMap.Clear();
}
// ✅ ELIMINADO: Reset del mapa de densidad de contactos ya no necesario
// ✅ ELIMINADO: ApplyZForceLimitation - ahora usamos intervención post-solver en OnSubstepEnded
@ -1597,95 +1571,8 @@ namespace CtrEditor.Simulacion
return lineStart + lineDirection * projectionLength;
}
/// <summary>
/// ✅ NUEVA FUNCIÓN: Discretiza una posición en regiones para el tracking de densidad de contactos
/// </summary>
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
{
var region = GetContactRegion(contactPosition);
lock (_contactsLock)
{
// Incrementar contador de contactos en esta región
if (_contactDensityMap.ContainsKey(region))
{
_contactDensityMap[region]++;
}
else
{
_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)
{
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: Funciones del sistema de limitación de fuerzas forceScale
// GetContactRegion, RegisterContactAndGetForceScale, GetContactPosition
// ✅ ELIMINADO: ApplyZForceLimitation - reemplazado por intervención post-solver en OnSubstepEnded