CtrEditor/Simulacion/simBotella.cs

275 lines
10 KiB
C#

using BepuPhysics.Collidables;
using BepuPhysics.Constraints;
using BepuPhysics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
namespace CtrEditor.Simulacion
{
public class simBotella : simBase
{
public float Radius;
public float Height; // Altura para la visualización del cilindro en Helix
private float _mass;
public bool Descartar = false;
public int isOnTransports;
public List<simBase> ListOnTransports;
public bool isRestricted;
public bool isNoMoreRestricted;
public simTransporte ConveyorRestrictedTo;
public float OverlapPercentage;
public float _neckRadius;
public bool isOnBrakeTransport; // Nueva propiedad para marcar si está en un transporte con freno
public simTransporte CurrentBrakeTransport { get; set; } // ✅ NUEVA: Referencia al transporte de freno actual
// ✅ NUEVO: Propiedades para control de flotación/presión
public float? LastTransportCollisionZ { get; set; }
public int PressureBuildup { get; set; } = 0;
public bool IsTouchingTransport { get; set; } = false;
// ✅ NUEVO: Propiedades para la física personalizada
public float BaseInverseMass { get; private set; }
public Vector3 InitialPosition3D { get; private set; }
private List<Action> _deferredActions;
public simBotella(Simulation simulation, List<Action> deferredActions, float diameter, Vector2 position, float mass, float neckRadius = 0)
{
_simulation = simulation;
_deferredActions = deferredActions;
Radius = diameter / 2f;
Height = diameter; // Altura igual al diámetro para mantener proporciones similares
_mass = mass;
_neckRadius = neckRadius;
ListOnTransports = new List<simBase>();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
var position3D = new Vector3(position.X, CoordinateConverter.WpfYToBepuY(position.Y), Radius + zPos_Transporte + zAltura_Transporte);
Create(position3D);
}
public float CenterX
{
get
{
return GetPosition().X;
}
set
{
var pos = GetPosition();
SetPosition(value, pos.Y, pos.Z);
}
}
public float CenterY
{
get
{
// ✅ USAR COORDINATECONVERTER para conversión centralizada
return CoordinateConverter.BepuYToWpfY(GetPosition().Y);
}
set
{
var pos = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
SetPosition(pos.X, CoordinateConverter.WpfYToBepuY(value), pos.Z);
}
}
public Vector2 Center
{
get
{
var pos3D = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
return CoordinateConverter.BepuVector3ToWpfVector2(pos3D);
}
set
{
// Mantener la Z actual, solo cambiar X, Y
var currentPos = GetPosition();
// ✅ USAR COORDINATECONVERTER para conversión centralizada
SetPosition(value.X, CoordinateConverter.WpfYToBepuY(value.Y), currentPos.Z);
}
}
public float Mass
{
get
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
return 1f / bodyReference.LocalInertia.InverseMass;
}
return _mass;
}
set
{
_mass = value;
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
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);
}
}
}
private void Create(Vector3 position)
{
RemoverBody();
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(),
defaultInertia,
new CollidableDescription(shapeIndex, 0),
activityDescription
);
BodyHandle = _simulation.Bodies.Add(bodyDescription);
_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;
}
public void SetDiameter(float diameter)
{
Radius = diameter / 2f;
Height = diameter; // Mantener altura igual al diámetro para proporciones consistentes
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var cylinder = new Cylinder(Radius, Height);
var shapeIndex = _simulation.Shapes.Add(cylinder);
ChangeBodyShape(shapeIndex);
}
}
public void SetHeight(float height)
{
Height = height;
// ✅ CORREGIDO: Usar ChangeBodyShape para limpiar la forma anterior
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var cylinder = new Cylinder(Radius, Height);
var shapeIndex = _simulation.Shapes.Add(cylinder);
ChangeBodyShape(shapeIndex);
}
}
public void SetMass(float mass)
{
Mass = mass;
}
/// <summary>
/// Limita la rotación de la botella solo al plano XY (siempre "de pie")
/// Esto simplifica enormemente la simulación y es más realista para botellas
/// </summary>
public void LimitRotationToXYPlane()
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
// Extraer solo la rotación en Z (plano XY) y eliminar las rotaciones en X e Y
var currentOrientation = bodyReference.Pose.Orientation;
// Convertir a ángulo en Z solamente
var rotationZ = GetRotationZ();
// ✅ 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;
// También limitar la velocidad angular a solo rotación en Z
var angularVelocity = bodyReference.Velocity.Angular;
bodyReference.Velocity.Angular = new Vector3(0, 0, angularVelocity.Z);
}
}
public void ApplyLinearVelocity(Vector3 velocity)
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
bodyReference.Velocity.Linear = velocity;
}
}
public Vector3 GetLinearVelocity()
{
if (_simulation != null && _simulation.Bodies.BodyExists(BodyHandle))
{
var bodyReference = _simulation.Bodies.GetBodyReference(BodyHandle);
return bodyReference.Velocity.Linear;
}
return Vector3.Zero;
}
public void CenterFixtureOnConveyor()
{
// Implementar lógica de centrado si es necesario
}
public bool IsOnAnyTransport()
{
return isOnTransports > 0;
}
/// <summary>
/// ✅ SIMPLIFICADO: RemoverBody que confía en BEPU para limpiar constraints
/// </summary>
public new void RemoverBody()
{
base.RemoverBody();
}
}
}