Se añadieron configuraciones para Hot Reload en CtrEditor.csproj y se ajustaron las propiedades de depuración en los grupos de configuración. En MainViewModel.cs, se corrigió un error en el tiempo de simulación. En Aether.cs, se implementaron nuevas propiedades y métodos para gestionar la presión entre botellas, mejorando la lógica de colisiones y evitando superposiciones. Estas mejoras optimizan la simulación y la interacción entre objetos.

This commit is contained in:
Miguel 2025-06-25 13:59:07 +02:00
parent 256d86aca5
commit 2cb90ec2dc
3 changed files with 262 additions and 9 deletions

View File

@ -7,14 +7,24 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<!-- Configuración para Hot Reload -->
<EnableHotReload>true</EnableHotReload>
<HotReloadLogger>true</HotReloadLogger>
<ForceGenerateMetadataAssemblyFormat>true</ForceGenerateMetadataAssemblyFormat>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize> <Optimize>False</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>True</Optimize> <Optimize>True</Optimize>
<DefineConstants>TRACE</DefineConstants>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -947,7 +947,7 @@ namespace CtrEditor
simSampleCount++; simSampleCount++;
// Eliminar el diseño de Debug luego de 2 segundos // Eliminar el diseño de Debug luego de 2 segundos
if (TiempoDesdeStartSimulacion > 12000) if (TiempoDesdeStartSimulacion > 1200)
simulationManager.Debug_ClearSimulationShapes(); simulationManager.Debug_ClearSimulationShapes();
else else
TiempoDesdeStartSimulacion += (float)elapsedMilliseconds; TiempoDesdeStartSimulacion += (float)elapsedMilliseconds;

View File

@ -138,12 +138,12 @@ namespace CtrEditor.Simulacion
Vector2 centerToBottle = bottle.Body.Position - Body.Position; Vector2 centerToBottle = bottle.Body.Position - Body.Position;
// Calculate angle of bottle relative to curve center (in radians) // Calculate angle of bottle relative to curve center (in radians)
float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X); float bottleAngle = (float)Math.Atan2(centerToBottle.Y, centerToBottle.X);
// Normalize all angles to the same range [0, 2π] // Normalize all angles to the same range [0, 2π]
float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle; float normalizedBottleAngle = bottleAngle < 0 ? bottleAngle + 2 * (float)Math.PI : bottleAngle;
float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle; float normalizedStartAngle = _startAngle < 0 ? _startAngle + 2 * (float)Math.PI : _startAngle;
float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle; float normalizedEndAngle = _endAngle < 0 ? _endAngle + 2 * (float)Math.PI : _endAngle;
// Handle case where the arc crosses the 0/2π boundary // Handle case where the arc crosses the 0/2π boundary
if (normalizedStartAngle > normalizedEndAngle) if (normalizedStartAngle > normalizedEndAngle)
{ {
@ -164,7 +164,7 @@ namespace CtrEditor.Simulacion
return 0; return 0;
// Calculate how far the bottle is from the edges using normalized angles // Calculate how far the bottle is from the edges using normalized angles
float angleFromStart, angleToEnd, totalAngle; float angleFromStart, angleToEnd, totalAngle;
if (normalizedStartAngle > normalizedEndAngle) if (normalizedStartAngle > normalizedEndAngle)
{ {
// Arc crosses 0/2π boundary // Arc crosses 0/2π boundary
@ -187,7 +187,7 @@ namespace CtrEditor.Simulacion
angleToEnd = normalizedEndAngle - normalizedBottleAngle; angleToEnd = normalizedEndAngle - normalizedBottleAngle;
totalAngle = normalizedEndAngle - normalizedStartAngle; totalAngle = normalizedEndAngle - normalizedStartAngle;
} }
// Use the minimum distance to either edge to calculate overlap // Use the minimum distance to either edge to calculate overlap
float minAngleDistance = Math.Min(angleFromStart, angleToEnd); float minAngleDistance = Math.Min(angleFromStart, angleToEnd);
// Calculate overlap percentage based on angle proximity to edges // Calculate overlap percentage based on angle proximity to edges
@ -514,6 +514,9 @@ namespace CtrEditor.Simulacion
public float OverlapPercentage; public float OverlapPercentage;
public float _neckRadius; public float _neckRadius;
PrismaticJoint _activeJoint; PrismaticJoint _activeJoint;
public bool isBeingPressedByOtherBottles;
public int bottleContactCount;
public float debugPenetrationLevel; // Para debug: nivel de penetración actual
private List<Action> _deferredActions; private List<Action> _deferredActions;
public simBotella(World world, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0) public simBotella(World world, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
{ {
@ -583,6 +586,8 @@ namespace CtrEditor.Simulacion
RemoverBody(); RemoverBody();
isOnTransports = 0; isOnTransports = 0;
_activeJoint = null; _activeJoint = null;
isBeingPressedByOtherBottles = false;
bottleContactCount = 0;
Body = _world.CreateCircle(Radius, 1f, position); Body = _world.CreateCircle(Radius, 1f, position);
Body.BodyType = BodyType.Dynamic; Body.BodyType = BodyType.Dynamic;
// Restablecer manejador de eventos de colisión // Restablecer manejador de eventos de colisión
@ -648,6 +653,11 @@ namespace CtrEditor.Simulacion
return true; // No aplicar respuestas físicas return true; // No aplicar respuestas físicas
} }
else if (fixtureB.Body.Tag is simBotella)
{
bottleContactCount++;
return true; // Permitir respuestas físicas entre botellas
}
return true; // No aplicar respuestas físicas return true; // No aplicar respuestas físicas
} }
@ -702,6 +712,11 @@ namespace CtrEditor.Simulacion
Sensor.LuzCortada -= 1; Sensor.LuzCortada -= 1;
Sensor.ListSimBotellaContact.Remove(this); Sensor.ListSimBotellaContact.Remove(this);
} }
if (fixtureB.Body.Tag is simBotella)
{
bottleContactCount = Math.Max(0, bottleContactCount - 1);
}
} }
/// <summary> /// <summary>
@ -753,8 +768,17 @@ namespace CtrEditor.Simulacion
return true; return true;
} }
// Aplicar la fuerza de fricción en función del porcentaje de superficie sobrepuesta // Si la botella está siendo presionada por múltiples botellas, reducir la aplicación de fuerza
Body.LinearVelocity += frictionCoefficient * desiredVelocity; float pressureReductionFactor = 1.0f;
if (isBeingPressedByOtherBottles)
{
// Reducir el factor basándose en la cantidad de contactos
pressureReductionFactor = Math.Max(0.1f, 1.0f / (1.0f + bottleContactCount * 0.5f));
}
// Aplicar la fuerza de fricción en función del porcentaje de superficie sobrepuesta y la presión
Vector2 finalVelocityChange = frictionCoefficient * desiredVelocity * pressureReductionFactor;
Body.LinearVelocity += finalVelocityChange;
//Body.ApplyForce(frictionForce * overlapPercentage); //Body.ApplyForce(frictionForce * overlapPercentage);
return false; return false;
} }
@ -842,6 +866,9 @@ namespace CtrEditor.Simulacion
_deferredActions = new List<Action>(); _deferredActions = new List<Action>();
stopwatch = new Stopwatch(); stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
// Configurar el callback PreSolve para manejar contactos entre botellas
world.ContactManager.PreSolve += OnPreSolve;
} }
public void Clear() public void Clear()
@ -862,6 +889,15 @@ namespace CtrEditor.Simulacion
public void Step() public void Step()
{ {
// Resetear las flags de presión al inicio del frame
ResetBottlePressureFlags();
// Verificar y corregir el espaciado entre botellas antes del step
PreventBottleOverlapping();
// Actualizar el estado de presión basado en contactos reales
UpdateBottlePressureStatus();
// Detener el cronómetro y obtener el tiempo transcurrido en milisegundos // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos
float elapsedMilliseconds = (float)(stopwatch.Elapsed.TotalMilliseconds - stopwatch_last); float elapsedMilliseconds = (float)(stopwatch.Elapsed.TotalMilliseconds - stopwatch_last);
stopwatch_last = stopwatch.Elapsed.TotalMilliseconds; stopwatch_last = stopwatch.Elapsed.TotalMilliseconds;
@ -876,7 +912,7 @@ namespace CtrEditor.Simulacion
{ {
botella.CenterFixtureOnConveyor(); botella.CenterFixtureOnConveyor();
botella.Body.Inertia = 0; botella.Body.Inertia = 0;
botella.Body.Mass = 100; botella.Body.Mass = botella.OriginalMass * 100;
} }
else if (botella.isNoMoreRestricted) else if (botella.isNoMoreRestricted)
{ {
@ -1048,5 +1084,212 @@ namespace CtrEditor.Simulacion
return polygon; return polygon;
} }
private void OnPreSolve(Contact contact, ref nkast.Aether.Physics2D.Collision.Manifold oldManifold)
{
var fixtureA = contact.FixtureA;
var fixtureB = contact.FixtureB;
// Verificar si el contacto es entre dos botellas
if (fixtureA.Body.Tag is simBotella botellaA && fixtureB.Body.Tag is simBotella botellaB)
{
// Calcular la distancia entre centros de las botellas
Vector2 centerA = botellaA.Body.Position;
Vector2 centerB = botellaB.Body.Position;
Vector2 direction = centerB - centerA;
float distance = direction.Length();
// Distancia mínima permitida (suma de radios + margen de seguridad)
float minDistance = botellaA.Radius + botellaB.Radius + 0.01f; // 1cm de margen
// Calcular la velocidad relativa
Vector2 relativeVelocity = botellaB.Body.LinearVelocity - botellaA.Body.LinearVelocity;
float relativeSpeed = relativeVelocity.Length();
// Solo marcar como presionadas si hay verdadera interpenetración O múltiples contactos
if (distance < minDistance * 0.95f) // Solo si están muy cerca (interpenetración significativa)
{
botellaA.isBeingPressedByOtherBottles = true;
botellaB.isBeingPressedByOtherBottles = true;
// Ajustar propiedades del contacto para reducir fuerzas explosivas
float penetrationRatio = Math.Max(0, (minDistance - distance) / minDistance);
// Reducir restitución progresivamente según la penetración
contact.Restitution = Math.Min(contact.Restitution, 0.1f * (1.0f - penetrationRatio));
// Aumentar fricción para disipar energía
contact.Friction = Math.Max(contact.Friction, 0.7f + penetrationRatio * 0.2f);
}
// Si la velocidad relativa es muy alta, también ajustar contacto
if (relativeSpeed > 2.0f)
{
contact.Restitution = 0.0f; // Sin rebote
contact.Friction = Math.Max(contact.Friction, 0.9f);
}
}
}
public void ResetBottlePressureFlags()
{
foreach (var cuerpo in Cuerpos)
{
if (cuerpo is simBotella botella)
{
botella.isBeingPressedByOtherBottles = false;
}
}
}
private void UpdateBottlePressureStatus()
{
var botellas = new List<simBotella>();
// Recopilar todas las botellas
foreach (var cuerpo in Cuerpos)
{
if (cuerpo is simBotella botella)
{
botellas.Add(botella);
}
}
// Verificar el estado de presión de cada botella basado en sus contactos reales
foreach (var botella in botellas)
{
int proximityCount = 0;
float totalPenetration = 0;
foreach (var otherBottle in botellas)
{
if (botella == otherBottle) continue;
Vector2 direction = otherBottle.Body.Position - botella.Body.Position;
float distance = direction.Length();
float minDistance = botella.Radius + otherBottle.Radius + 0.01f; // 1cm margen
if (distance < minDistance)
{
proximityCount++;
totalPenetration += (minDistance - distance);
}
}
// Actualizar información de debug
botella.debugPenetrationLevel = totalPenetration;
// Solo marcar como presionada si tiene múltiples contactos cercanos O penetración significativa
// Y además la botella está en un transportador (para evitar marcar botellas libres)
if ((proximityCount >= 2 || totalPenetration > 0.01f) && botella.IsOnAnyTransport())
{
botella.isBeingPressedByOtherBottles = true;
}
}
}
private void PreventBottleOverlapping()
{
var botellas = new List<simBotella>();
// Recopilar todas las botellas
foreach (var cuerpo in Cuerpos)
{
if (cuerpo is simBotella botella)
{
botellas.Add(botella);
}
}
// Verificar cada par de botellas múltiples veces para resolver interpenetraciones complejas
int iterations = 3; // Múltiples iteraciones para resolver conflictos
for (int iter = 0; iter < iterations; iter++)
{
bool anyCorrection = false;
for (int i = 0; i < botellas.Count; i++)
{
for (int j = i + 1; j < botellas.Count; j++)
{
var botellaA = botellas[i];
var botellaB = botellas[j];
Vector2 centerA = botellaA.Body.Position;
Vector2 centerB = botellaB.Body.Position;
Vector2 direction = centerB - centerA;
float distance = direction.Length();
// Distancia mínima permitida (suma de radios + margen de seguridad)
float minDistance = botellaA.Radius + botellaB.Radius + 0.005f; // 5mm de margen
// Si están demasiado cerca
if (distance < minDistance && distance > 0.001f)
{
anyCorrection = true;
// Normalizar la dirección
direction.Normalize();
// Calcular la corrección necesaria
float penetration = minDistance - distance;
float correction = penetration * 0.5f;
// Factor de corrección que decrece con las iteraciones
float correctionFactor = 0.3f / (iter + 1); // Más agresivo en la primera iteración
Vector2 correctionVector = direction * correction * correctionFactor;
// Aplicar corrección considerando masas (botellas más pesadas se mueven menos)
float massA = botellaA.Body.Mass;
float massB = botellaB.Body.Mass;
float totalMass = massA + massB;
float ratioA = massB / totalMass; // Botella más liviana se mueve más
float ratioB = massA / totalMass;
// Solo mover si las botellas no están restringidas por transportadores
if (!botellaA.isRestricted)
{
Vector2 newPosA = centerA - correctionVector * ratioA;
if (!float.IsNaN(newPosA.X) && !float.IsNaN(newPosA.Y))
{
botellaA.Body.Position = newPosA;
}
}
if (!botellaB.isRestricted)
{
Vector2 newPosB = centerB + correctionVector * ratioB;
if (!float.IsNaN(newPosB.X) && !float.IsNaN(newPosB.Y))
{
botellaB.Body.Position = newPosB;
}
}
// Solo marcar como presionadas si hay interpenetración significativa
if (penetration > 0.005f) // Solo si penetran más de 5mm
{
botellaA.isBeingPressedByOtherBottles = true;
botellaB.isBeingPressedByOtherBottles = true;
}
// Reducir velocidades para evitar acumulación de energía
float velocityDamping = 0.9f;
if (penetration > 0.01f) // Si la penetración es significativa (> 1cm)
{
velocityDamping = 0.7f; // Reducción más agresiva
}
botellaA.Body.LinearVelocity *= velocityDamping;
botellaB.Body.LinearVelocity *= velocityDamping;
}
}
}
// Si no hubo correcciones en esta iteración, podemos parar
if (!anyCorrection)
break;
}
}
} }
} }