351 lines
16 KiB
C#
351 lines
16 KiB
C#
using BepuPhysics.Collidables;
|
|
using BepuPhysics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CtrEditor.Simulacion
|
|
{
|
|
/// <summary>
|
|
/// Representa una curva o arco en la simulación física.
|
|
/// ✅ IMPORTANTE: Los ángulos _startAngle y _endAngle se almacenan INTERNAMENTE en radianes BEPU
|
|
/// (ya convertidos desde grados WPF usando CoordinateConverter.WpfDegreesToBepuRadians)
|
|
/// Las propiedades públicas StartAngle/EndAngle devuelven grados WPF usando CoordinateConverter.BepuRadiansToWpfDegrees
|
|
/// ✅ NUEVO: Cada triángulo actúa como un mini-transporte con su propia dirección tangencial fija
|
|
/// </summary>
|
|
public class simCurve : simBase
|
|
{
|
|
private float _innerRadius;
|
|
private float _outerRadius;
|
|
private float _startAngle; // ✅ SIEMPRE en radianes BEPU
|
|
private float _endAngle; // ✅ SIEMPRE en radianes BEPU
|
|
public float Speed { get; set; } // Velocidad para efectos de cinta transportadora (m/s)
|
|
|
|
private List<Action> _deferredActions;
|
|
|
|
// ✅ NUEVO: Almacenar el centro real de la curva
|
|
private Vector3 _curveCenter;
|
|
|
|
// ✅ 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;
|
|
|
|
public float InnerRadius => _innerRadius;
|
|
public float OuterRadius => _outerRadius;
|
|
public float StartAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_startAngle); // Convertir de radianes BEPU internos a grados WPF
|
|
public float EndAngle => CoordinateConverter.BepuRadiansToWpfDegrees(_endAngle); // Convertir de radianes BEPU internos a grados WPF
|
|
|
|
// ✅ NUEVO: Propiedad para acceder al centro real de la curva
|
|
public Vector3 CurveCenter => _curveCenter;
|
|
|
|
public simCurve(Simulation simulation, List<Action> deferredActions, float innerRadius, float outerRadius, float startAngle, float endAngle, Vector2 topLeft, float unused = 0, SimulationManagerBEPU simulationManager = null)
|
|
{
|
|
_simulation = simulation;
|
|
_deferredActions = deferredActions;
|
|
_simulationManager = simulationManager; // ✅ NUEVO: Almacenar referencia
|
|
_innerRadius = innerRadius;
|
|
_outerRadius = outerRadius;
|
|
// ✅ SIMPLIFICADO: Usar conversión directa WPF grados → BEPU radianes
|
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
|
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
|
|
|
// ✅ NUEVO: Calcular y almacenar el centro real de la curva
|
|
var curveSize = outerRadius * 2f;
|
|
var zPosition = zPos_Curve;
|
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(topLeft, curveSize, curveSize, 0f, zPosition);
|
|
|
|
// ✅ SIMPLIFICADO: Crear la curva directamente
|
|
Create(innerRadius, outerRadius, startAngle, endAngle, topLeft, 0);
|
|
}
|
|
|
|
// ✅ SIMPLIFICADO: Configurar velocidad angular para AngularAxisMotor
|
|
public void SetSpeed(float speed)
|
|
{
|
|
Speed = speed; // Velocidad angular directa (sin inversión)
|
|
OnSpeedChanged?.Invoke(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Detiene completamente la curva
|
|
/// </summary>
|
|
public void StopCurve()
|
|
{
|
|
SetSpeed(0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Invierte la dirección de la curva manteniendo la misma velocidad
|
|
/// </summary>
|
|
public void ReverseCurve()
|
|
{
|
|
SetSpeed(-Speed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Actualiza tanto posición como rotación desde parámetros WPF
|
|
/// </summary>
|
|
internal void UpdateFromWpfParameters(Vector2 wpfTopLeft, float wpfAngle, float innerRadius, float outerRadius, float startAngle, float endAngle)
|
|
{
|
|
// Actualizar parámetros de la curva
|
|
_innerRadius = innerRadius;
|
|
_outerRadius = outerRadius;
|
|
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
|
_endAngle = CoordinateConverter.WpfDegreesToBepuRadians(endAngle);
|
|
|
|
// ✅ NUEVO: Actualizar el centro real de la curva
|
|
var curveSize = outerRadius * 2f;
|
|
var zPosition = zPos_Curve;
|
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
|
|
|
Create(_curveCenter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Actualiza posición desde Top-Left WPF manteniendo parámetros actuales
|
|
/// </summary>
|
|
internal void SetPositionFromWpfTopLeft(Vector2 wpfTopLeft)
|
|
{
|
|
var curveSize = _outerRadius * 2f;
|
|
var zPosition = GetPosition().Z; // Mantener Z actual
|
|
_curveCenter = CoordinateConverter.CalculateBepuCenterFromWpfTopLeft(wpfTopLeft, curveSize, curveSize, 0f, zPosition);
|
|
|
|
Create(_curveCenter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Obtiene Top-Left WPF desde la posición actual
|
|
/// </summary>
|
|
internal Vector2 GetWpfTopLeft()
|
|
{
|
|
var curveSize = _outerRadius * 2f;
|
|
return CoordinateConverter.CalculateWpfTopLeftFromBepuCenter(_curveCenter, curveSize, curveSize, 0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ CORREGIDO: Obtiene los triángulos reales creados para la curva
|
|
/// Devuelve los triángulos en coordenadas locales para evitar transformación duplicada
|
|
/// </summary>
|
|
public Triangle[] GetRealBEPUTriangles()
|
|
{
|
|
try
|
|
{
|
|
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] No hay triángulos almacenados");
|
|
return new Triangle[0];
|
|
}
|
|
|
|
// ✅ CORREGIDO: Devolver triángulos en coordenadas locales
|
|
// La visualización 3D aplicará la transformación una sola vez
|
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Devolviendo {_storedTriangles.Length} triángulos en coordenadas locales");
|
|
return _storedTriangles;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[GetRealBEPUTriangles] Error: {ex.Message}");
|
|
return new Triangle[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Obtiene los triángulos transformados a coordenadas mundiales (solo para debugging)
|
|
/// </summary>
|
|
public Triangle[] GetWorldBEPUTriangles()
|
|
{
|
|
try
|
|
{
|
|
if (_storedTriangles == null || _storedTriangles.Length == 0)
|
|
{
|
|
return new Triangle[0];
|
|
}
|
|
|
|
var worldTriangles = TransformTrianglesToWorldCoordinates(_storedTriangles);
|
|
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Devolviendo {worldTriangles.Length} triángulos en coordenadas mundiales");
|
|
return worldTriangles;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[GetWorldBEPUTriangles] Error: {ex.Message}");
|
|
return new Triangle[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Transforma triángulos de coordenadas locales a mundiales
|
|
/// </summary>
|
|
private Triangle[] TransformTrianglesToWorldCoordinates(Triangle[] localTriangles)
|
|
{
|
|
try
|
|
{
|
|
if (_simulation == null || !_simulation.Bodies.BodyExists(BodyHandle))
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Cuerpo no existe, devolviendo triángulos locales");
|
|
return localTriangles; // Fallback: devolver triángulos sin transformar
|
|
}
|
|
|
|
var body = _simulation.Bodies[BodyHandle];
|
|
var bodyPosition = body.Pose.Position;
|
|
var bodyOrientation = body.Pose.Orientation;
|
|
|
|
var transformedTriangles = new Triangle[localTriangles.Length];
|
|
|
|
for (int i = 0; i < localTriangles.Length; i++)
|
|
{
|
|
var localTriangle = localTriangles[i];
|
|
|
|
// Transformar cada vértice del triángulo a coordenadas mundiales
|
|
var worldA = bodyPosition + Vector3.Transform(localTriangle.A, bodyOrientation);
|
|
var worldB = bodyPosition + Vector3.Transform(localTriangle.B, bodyOrientation);
|
|
var worldC = bodyPosition + Vector3.Transform(localTriangle.C, bodyOrientation);
|
|
|
|
transformedTriangles[i] = new Triangle(worldA, worldB, worldC);
|
|
}
|
|
|
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Transformados {transformedTriangles.Length} triángulos. Body pos: {bodyPosition}");
|
|
return transformedTriangles;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[TransformTriangles] Error: {ex.Message}, devolviendo triángulos locales");
|
|
return localTriangles; // Fallback en caso de error
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// ✅ SIMPLIFICADO: Solo remover el cuerpo principal (Mesh único)
|
|
base.RemoverBody();
|
|
}
|
|
|
|
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
|
|
// No hay rotación separada del objeto
|
|
|
|
// Actualizar parámetros internos
|
|
_innerRadius = innerRadius;
|
|
_outerRadius = outerRadius;
|
|
// ✅ CORREGIDO: Usar conversión directa WPF grados → BEPU radianes
|
|
_startAngle = CoordinateConverter.WpfDegreesToBepuRadians(startAngle);
|
|
_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
|
|
}
|
|
|
|
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)
|
|
{
|
|
// ✅ 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");
|
|
}
|
|
}
|
|
|
|
// ✅ 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
|
|
|
|
/// <summary>
|
|
/// ✅ SIMPLIFICADO: Crea triángulos usando Triangle de BEPU directamente
|
|
/// Solo superficie superior, eliminando complejidad innecesaria
|
|
/// Los triángulos se crean en coordenadas locales (Z=0) y la posición del cuerpo los ubica correctamente
|
|
/// </summary>
|
|
/// <param name="innerRadius">Radio interno del arco</param>
|
|
/// <param name="outerRadius">Radio externo del arco</param>
|
|
/// <param name="startAngle">Ángulo inicial en radianes BEPU</param>
|
|
/// <param name="endAngle">Ángulo final en radianes BEPU</param>
|
|
/// <returns>Array de triángulos nativos de BEPU en coordenadas locales</returns>
|
|
private Triangle[] CreateSimpleArcTriangles(float innerRadius, float outerRadius, float startAngle, float endAngle)
|
|
{
|
|
var triangles = new List<Triangle>();
|
|
|
|
// ✅ SIMPLIFICADO: Menos segmentos, menos complejidad
|
|
float arcLength = Math.Abs(endAngle - startAngle) * ((innerRadius + outerRadius) / 2f);
|
|
int segments = Math.Max(8, Math.Min((int)(arcLength * 8), 32)); // Menos segmentos
|
|
float angleStep = (endAngle - startAngle) / segments;
|
|
|
|
// ✅ SIMPLIFICADO: Sin inversión compleja de ángulos
|
|
for (int i = 0; i < segments; i++)
|
|
{
|
|
float angle1 = startAngle + i * angleStep;
|
|
float angle2 = startAngle + (i + 1) * angleStep;
|
|
|
|
// ✅ COORDENADAS LOCALES: Triángulos en Z=0, el cuerpo los posiciona correctamente
|
|
var inner1 = new Vector3(innerRadius * (float)Math.Cos(angle1), innerRadius * (float)Math.Sin(angle1), 0);
|
|
var outer1 = new Vector3(outerRadius * (float)Math.Cos(angle1), outerRadius * (float)Math.Sin(angle1), 0);
|
|
var inner2 = new Vector3(innerRadius * (float)Math.Cos(angle2), innerRadius * (float)Math.Sin(angle2), 0);
|
|
var outer2 = new Vector3(outerRadius * (float)Math.Cos(angle2), outerRadius * (float)Math.Sin(angle2), 0);
|
|
|
|
// ✅ USAR Triangle NATIVO DE BEPU (solo superficie superior)
|
|
triangles.Add(new Triangle(inner1, outer1, outer2));
|
|
triangles.Add(new Triangle(inner1, outer2, inner2));
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
}
|