CtrEditor/Simulacion/BEPUVisualization3D.cs

1590 lines
68 KiB
C#

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using BepuPhysics;
using BepuPhysics.Collidables;
using HelixToolkit.Wpf;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows;
namespace CtrEditor.Simulacion
{
/// <summary>
/// Estructura para almacenar las dimensiones de las formas para detectar cambios
/// </summary>
public struct ShapeDimensions
{
public float Width { get; set; }
public float Height { get; set; }
public float Length { get; set; }
public float Radius { get; set; }
public int ShapeType { get; set; }
// ✅ NUEVO: Parámetros específicos para curvas
public float InnerRadius { get; set; }
public float OuterRadius { get; set; }
public float StartAngle { get; set; }
public float EndAngle { get; set; }
// ✅ NUEVO: Estado de movimiento para transportes
public bool IsMoving { get; set; }
public bool Equals(ShapeDimensions other)
{
return Math.Abs(Width - other.Width) < 0.001f &&
Math.Abs(Height - other.Height) < 0.001f &&
Math.Abs(Length - other.Length) < 0.001f &&
Math.Abs(Radius - other.Radius) < 0.001f &&
Math.Abs(InnerRadius - other.InnerRadius) < 0.001f &&
Math.Abs(OuterRadius - other.OuterRadius) < 0.001f &&
Math.Abs(StartAngle - other.StartAngle) < 0.001f &&
Math.Abs(EndAngle - other.EndAngle) < 0.001f &&
ShapeType == other.ShapeType &&
IsMoving == other.IsMoving;
}
}
/// <summary>
/// Manager que sincroniza el mundo 3D de BEPU con la visualización HelixToolkit
/// Usa simBase como clave única para evitar problemas de reutilización de BodyHandle
///
/// ✅ NUEVO: Funcionalidad de Debug para Triángulos
/// Para activar la visualización de triángulos individuales de curvas:
///
/// // Activar modo debug
/// visualizationManager.SetDebugTrianglesMode(true);
///
/// // Desactivar modo debug (volver a superficie continua)
/// visualizationManager.SetDebugTrianglesMode(false);
///
/// // Verificar si está activo
/// bool isDebugActive = visualizationManager.IsDebugTrianglesModeEnabled();
///
/// En modo debug:
/// - Cada triángulo de BEPU se muestra separado con un pequeño offset
/// - Los triángulos tienen bordes wireframe para mejor visualización
/// - Se usa un material naranja semi-transparente para distinguir del modo normal
/// - Se muestran mensajes de debug en la consola con información detallada
/// </summary>
public class BEPUVisualization3DManager
{
private HelixViewport3D viewport3D;
private SimulationManagerBEPU simulationManager;
private Dictionary<simBase, ModelVisual3D> simBaseToModelMap;
private Dictionary<simBase, ShapeDimensions> lastKnownDimensions;
// ✅ NUEVO: Diccionario para gestionar animaciones activas
private Dictionary<simBase, Storyboard> activeAnimations;
// ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar)
public static bool DebugShowIndividualTriangles { get; set; } = false;
public HelixViewport3D Viewport3D
{
get => viewport3D;
set => viewport3D = value;
}
public BEPUVisualization3DManager(HelixViewport3D viewport, SimulationManagerBEPU simManager)
{
if (viewport == null)
throw new ArgumentNullException(nameof(viewport), "HelixViewport3D cannot be null");
if (simManager == null)
throw new ArgumentNullException(nameof(simManager), "SimulationManagerBEPU cannot be null");
viewport3D = viewport;
simulationManager = simManager;
simBaseToModelMap = new Dictionary<simBase, ModelVisual3D>();
lastKnownDimensions = new Dictionary<simBase, ShapeDimensions>();
activeAnimations = new Dictionary<simBase, Storyboard>();
InitializeViewport();
}
private void InitializeViewport()
{
if (viewport3D == null)
{
System.Diagnostics.Debug.WriteLine($"[3D Init] ERROR: viewport3D is null");
return;
}
try
{
// Configurar la cámara usando el estado guardado
LoadCameraState();
// Agregar luces
var directionalLight = new DirectionalLight
{
Color = Color.FromRgb(150, 150, 150), // Luz direccional menos intensa
Direction = new Vector3D(-0.2, -0.3, -1) // Ligeramente angulada para evitar reflejos directos
};
viewport3D.Children.Add(new ModelVisual3D { Content = directionalLight });
var ambientLight = new AmbientLight
{
Color = Color.FromRgb(120, 120, 120) // Más luz ambiente para suavizar sombras
};
viewport3D.Children.Add(new ModelVisual3D { Content = ambientLight });
// Suscribirse a eventos para detectar cambios en la cámara
SubscribeToCameraEvents();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Init] ERROR initializing viewport: {ex.Message}");
}
}
/// <summary>
/// Sincroniza usando los objetos simBase del SimulationManager como fuente de verdad
/// Esto garantiza que cada objeto único tiene su propia visualización
/// </summary>
public void SynchronizeWorld()
{
// Validaciones críticas de null
if (simulationManager == null)
{
System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: simulationManager is null");
return;
}
if (simulationManager.simulation == null)
{
System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: simulation is null");
return;
}
if (viewport3D == null)
{
System.Diagnostics.Debug.WriteLine($"[3D Sync] ERROR: viewport3D is null");
return;
}
// Limpiar objetos que ya no existen
CleanupRemovedObjects();
// Sincronizar cada objeto simBase
foreach (var simObj in simulationManager.Cuerpos)
{
// ✅ NUEVO: Caso especial para simBarrera (no tiene BodyHandle físico)
if (simObj is simBarrera barrera)
{
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🔍 Procesando simBarrera...");
if (simBaseToModelMap.ContainsKey(simObj))
{
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🔄 Actualizando visualización existente de barrera");
// Actualizar visualización existente de barrera
UpdateBarreraVisualization(barrera);
}
else
{
//System.Diagnostics.Debug.WriteLine($"[3D Sync] 🆕 Creando nueva visualización de barrera");
// Crear nueva visualización de barrera
CreateBarreraVisualization(barrera);
}
continue;
}
// Para objetos con BodyHandle físico
if (simObj == null || !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
continue;
if (simBaseToModelMap.ContainsKey(simObj))
{
// Actualizar visualización existente
UpdateVisualizationFromSimBase(simObj);
}
else
{
// Crear nueva visualización
CreateVisualizationFromSimBase(simObj);
}
}
}
private void CleanupRemovedObjects()
{
if (simulationManager?.Cuerpos == null || viewport3D == null)
return;
var objectsToRemove = new List<simBase>();
try
{
foreach (var kvp in simBaseToModelMap)
{
var simObj = kvp.Key;
var model = kvp.Value;
// ✅ NUEVO: Caso especial para simBarrera (no tiene BodyHandle físico)
if (simObj is simBarrera)
{
// Verificar si la barrera aún está en la lista de cuerpos activos
if (!simulationManager.Cuerpos.Contains(simObj))
{
if (model != null)
{
viewport3D.Children.Remove(model);
}
objectsToRemove.Add(simObj);
}
}
else
{
// Para objetos con BodyHandle físico
if (!simulationManager.Cuerpos.Contains(simObj) ||
!simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
{
if (model != null)
{
viewport3D.Children.Remove(model);
}
objectsToRemove.Add(simObj);
}
}
}
foreach (var simObj in objectsToRemove)
{
// ✅ NUEVO: Limpiar animaciones activas para objetos eliminados
if (simObj is simTransporte transporteToRemove && activeAnimations.ContainsKey(transporteToRemove))
{
StopTransportAnimation(transporteToRemove);
}
simBaseToModelMap.Remove(simObj);
lastKnownDimensions.Remove(simObj);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Cleanup] ERROR during cleanup: {ex.Message}");
}
}
private void CreateVisualizationFromSimBase(simBase simObj)
{
if (simulationManager?.simulation == null || !simulationManager.simulation.Bodies.BodyExists(simObj.BodyHandle))
{
System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: Body does not exist for simBase");
return;
}
try
{
var body = simulationManager.simulation.Bodies[simObj.BodyHandle];
var collidable = body.Collidable;
var shapeIndex = collidable.Shape;
if (!shapeIndex.Exists)
{
System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: Shape does not exist");
return;
}
ModelVisual3D visual3D = null;
// Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro
if (simObj is simBotella botella)
{
visual3D = CreateCylinderVisualization(botella.Radius, botella.Height, simObj);
}
// Caso especial: simCurve usa múltiples triángulos pero se visualiza como superficie curva
else if (simObj is simCurve curve)
{
visual3D = CreateCurveVisualization(curve);
}
else
{
// Para otros objetos, usar la forma real de BEPU
var dimensions = GetShapeDimensions(shapeIndex, simObj);
if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id)
{
visual3D = CreateSphereVisualization(dimensions.Radius, simObj);
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Box.Id)
{
visual3D = CreateBoxVisualization(dimensions.Width, dimensions.Height, dimensions.Length, simObj);
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Cylinder.Id)
{
visual3D = CreateCylinderVisualization(dimensions.Radius, dimensions.Length, simObj);
}
else
{
System.Diagnostics.Debug.WriteLine($"[3D Create] WARNING: Unsupported shape type: {dimensions.ShapeType}");
return;
}
}
if (visual3D != null)
{
// Posicionar correctamente el objeto 3D
var position = simObj.GetPosition();
var rotationZ = simObj.GetRotationZ();
var transform = new Transform3DGroup();
transform.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), rotationZ * 180.0 / Math.PI)));
transform.Children.Add(new TranslateTransform3D(position.X, position.Y, position.Z));
visual3D.Transform = transform;
// Agregar a la vista 3D y asociar con simBase
viewport3D.Children.Add(visual3D);
simBaseToModelMap[simObj] = visual3D;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Create] ERROR: {ex.Message}");
}
}
private ModelVisual3D CreateSphereVisualization(float radius, simBase simObj = null)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddSphere(new Point3D(0, 0, 0), (double)radius, 16, 16);
var model = new GeometryModel3D(meshBuilder.ToMesh(), GetMaterialForSimBase(simObj));
return new ModelVisual3D { Content = model };
}
private ModelVisual3D CreateBoxVisualization(float width, float height, float length, simBase simObj = null)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddBox(new Point3D(0, 0, 0), (double)width, (double)height, (double)length);
var model = new GeometryModel3D(meshBuilder.ToMesh(), GetMaterialForSimBase(simObj));
return new ModelVisual3D { Content = model };
}
private ModelVisual3D CreateCapsuleVisualization(float radius, float length)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddCylinder(new Point3D(0, 0, -length / 2), new Point3D(0, 0, length / 2), radius, 16);
// Material plástico brillante para cápsulas (amarillo)
var material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(255, 255, 80)),
specularPower: 125,
ambient: 210
);
var model = new GeometryModel3D(meshBuilder.ToMesh(), material);
return new ModelVisual3D { Content = model };
}
private ModelVisual3D CreateCylinderVisualization(float radius, float length, simBase simObj = null)
{
var meshBuilder = new MeshBuilder();
// Crear cilindro con tapas usando la versión mejorada
var p1 = new Point3D(0, 0, -length / 2);
var p2 = new Point3D(0, 0, length / 2);
meshBuilder.AddCylinder(p1, p2, radius: (double)radius, thetaDiv: 16, cap1: true, cap2: true);
var geometry = meshBuilder.ToMesh();
var model = new GeometryModel3D(geometry, GetMaterialForSimBase(simObj));
var visual = new ModelVisual3D();
visual.Content = model;
return visual;
}
private ModelVisual3D CreateCurveVisualization(simCurve curve)
{
try
{
var meshBuilder = new MeshBuilder();
// ✅ NUEVO: Elegir entre visualización normal o debug según el flag
if (DebugShowIndividualTriangles)
{
CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve);
}
else
{
// ✅ CORREGIDO: Crea un arco de mesh continuo y simple desde los triángulos de BEPU
CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
}
var geometry = meshBuilder.ToMesh();
// ✅ NUEVO: Usar material específico para debug si está activo
Material material = DebugShowIndividualTriangles ?
GetDebugMaterialForCurve(curve) :
GetMaterialForSimBase(curve);
var model = new GeometryModel3D(geometry, material);
var visual = new ModelVisual3D();
visual.Content = model;
return visual;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D CreateCurve] Error creating curve visualization: {ex.Message}");
return null;
}
}
/// <summary>
/// ✅ CORREGIDO: Crea un arco de mesh continuo y simple desde los triángulos de BEPU
/// Genera una superficie plana que forma un arco suave y eficiente
/// </summary>
private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve)
{
try
{
// Obtener los triángulos de BEPU
var localTriangles = curve.GetRealBEPUTriangles();
System.Diagnostics.Debug.WriteLine($"[3D Curve] Creating continuous arc mesh from {localTriangles.Length} BEPU triangles");
if (localTriangles.Length == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Curve] WARNING: No triangles found in BEPU curve");
return;
}
// ✅ CORREGIDO: Usar altura más visible y verificar coordenadas
const float curveHeight = 0.0f; // 0.02f; // 2cm de altura para que sea claramente visible
int triangleCount = 0;
foreach (var triangle in localTriangles)
{
// ✅ LOGGING: Verificar las coordenadas del primer triángulo
if (triangleCount == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: A({triangle.A.X:F3}, {triangle.A.Y:F3}, {triangle.A.Z:F3})");
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: B({triangle.B.X:F3}, {triangle.B.Y:F3}, {triangle.B.Z:F3})");
System.Diagnostics.Debug.WriteLine($"[3D Curve] First triangle: C({triangle.C.X:F3}, {triangle.C.Y:F3}, {triangle.C.Z:F3})");
}
// Crear triángulo en la superficie superior con altura visible
var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z + curveHeight);
var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z + curveHeight);
var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z + curveHeight);
// ✅ CORREGIDO: Probar ambos órdenes de vértices para asegurar normales correctas
meshBuilder.AddTriangle(pointA, pointB, pointC);
// También agregar el triángulo con orden inverso para que sea visible desde ambos lados
meshBuilder.AddTriangle(pointA, pointC, pointB);
triangleCount++;
}
System.Diagnostics.Debug.WriteLine($"[3D Curve] ✅ Continuous arc mesh created with {triangleCount} triangles (height: {curveHeight})");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Curve] ERROR creating continuous arc mesh: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[3D Curve] Stack trace: {ex.StackTrace}");
}
}
/// <summary>
/// ✅ CORREGIDO: Función de debug que muestra triángulos reales de BEPU sin offset artificial
/// Los triángulos se muestran exactamente como están en la física (planos, sin separación)
/// </summary>
private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve)
{
try
{
// ✅ EXTRAER TRIÁNGULOS LOCALES DE BEPU PARA DEBUG
var localTriangles = curve.GetRealBEPUTriangles();
if (localTriangles.Length == 0)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No hay triángulos locales para debug, usando fallback");
CreateBasicDebugGeometry(meshBuilder, curve);
return;
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] Creando debug de {localTriangles.Length} triángulos reales planos");
// ✅ MOSTRAR TRIÁNGULOS REALES SIN OFFSET ARTIFICIAL
for (int i = 0; i < localTriangles.Length; i++)
{
var triangle = localTriangles[i];
// ✅ USAR TRIÁNGULOS EXACTOS SIN MODIFICACIÓN
var pointA = new Point3D(triangle.A.X, triangle.A.Y, triangle.A.Z);
var pointB = new Point3D(triangle.B.X, triangle.B.Y, triangle.B.Z);
var pointC = new Point3D(triangle.C.X, triangle.C.Y, triangle.C.Z);
// Agregar triángulo real sin modificaciones
meshBuilder.AddTriangle(pointA, pointB, pointC);
// ✅ DEBUG: Agregar bordes delgados para visualizar límites entre triángulos
AddDebugTriangleEdges(meshBuilder, pointA, pointB, pointC, 0.002f);
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] ✅ Debug mesh creado con triángulos reales planos (sin offset artificial)");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating debug mesh: {ex.Message}");
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to basic debug geometry");
CreateBasicDebugGeometry(meshBuilder, curve);
}
}
/// <summary>
/// ✅ NUEVO: Agrega bordes debug a un triángulo individual
/// </summary>
private void AddDebugTriangleEdges(MeshBuilder meshBuilder, Point3D a, Point3D b, Point3D c, double edgeThickness)
{
try
{
// Crear cilindros delgados para los bordes del triángulo
meshBuilder.AddCylinder(a, b, edgeThickness, 4, false, false);
meshBuilder.AddCylinder(b, c, edgeThickness, 4, false, false);
meshBuilder.AddCylinder(c, a, edgeThickness, 4, false, false);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR adding triangle edges: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual
/// </summary>
private void AddDebugEdge(MeshBuilder meshBuilder, Point3D p1Top, Point3D p2Top, Point3D p1Bottom, Point3D p2Bottom, float thickness)
{
try
{
// Crear un cilindro muy delgado como borde
meshBuilder.AddCylinder(p1Top, p2Top, thickness, 4, false, false);
meshBuilder.AddCylinder(p1Bottom, p2Bottom, thickness, 4, false, false);
meshBuilder.AddCylinder(p1Top, p1Bottom, thickness, 4, false, false);
meshBuilder.AddCylinder(p2Top, p2Bottom, thickness, 4, false, false);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR adding debug edge: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Geometría básica de debug cuando fallan otras opciones
/// </summary>
private void CreateBasicDebugGeometry(MeshBuilder meshBuilder, simCurve curve)
{
try
{
// Crear una geometría muy simple que indique que hay un problema
float innerRadius = curve.InnerRadius;
float outerRadius = curve.OuterRadius;
float centerRadius = (innerRadius + outerRadius) / 2f;
// Crear un triángulo básico como indicador de debug
var p1 = new Point3D(centerRadius, 0, 0);
var p2 = new Point3D(centerRadius * Math.Cos(Math.PI/3), centerRadius * Math.Sin(Math.PI/3), 0);
var p3 = new Point3D(centerRadius * Math.Cos(-Math.PI/3), centerRadius * Math.Sin(-Math.PI/3), 0);
meshBuilder.AddTriangle(p1, p2, p3);
System.Diagnostics.Debug.WriteLine($"[3D Debug] Created basic debug geometry as fallback");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating basic debug geometry: {ex.Message}");
}
}
private void UpdateVisualizationFromSimBase(simBase simObj)
{
if (!simBaseToModelMap.TryGetValue(simObj, out var visual))
return;
if (simulationManager?.simulation == null || visual == null)
return;
try
{
var body = simulationManager.simulation.Bodies[simObj.BodyHandle];
var pose = body.Pose;
var collidable = body.Collidable;
// Verificar si las dimensiones han cambiado
var currentDimensions = GetShapeDimensions(collidable.Shape, simObj);
bool dimensionsChanged = false;
if (lastKnownDimensions.TryGetValue(simObj, out var lastDimensions))
{
dimensionsChanged = !currentDimensions.Equals(lastDimensions);
}
else
{
dimensionsChanged = true; // Primera vez
}
// Si las dimensiones cambiaron, recrear la geometría
if (dimensionsChanged)
{
RecreateVisualizationGeometry(simObj, visual, currentDimensions);
lastKnownDimensions[simObj] = currentDimensions;
}
else
{
// System.Diagnostics.Debug.WriteLine($"[3D Update] No geometry changes needed for {simObj.GetType().Name}");
}
// ✅ NUEVO: Gestionar animaciones para transportes
if (simObj is simTransporte transporte)
{
ManageTransportAnimation(transporte, visual);
}
// Actualizar transformación del modelo 3D
var transform = new Transform3DGroup();
// ✅ ORDEN CORRECTO: Primero Rotación, luego Traslación
// Esto mantiene el pivot consistente con WPF (Top-Left)
// 1. Rotación (primero, en el origen)
var rotation = new QuaternionRotation3D(new System.Windows.Media.Media3D.Quaternion(
(double)pose.Orientation.X,
(double)pose.Orientation.Y,
(double)pose.Orientation.Z,
(double)pose.Orientation.W
));
var rotationTransform = new RotateTransform3D(rotation);
transform.Children.Add(rotationTransform);
// 2. Traslación (después, mueve el objeto ya rotado)
var translation = new TranslateTransform3D(
(double)pose.Position.X,
(double)pose.Position.Y,
(double)pose.Position.Z
);
transform.Children.Add(translation);
visual.Transform = transform;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Update] ERROR updating visualization: {ex.Message}");
}
}
private ShapeDimensions GetShapeDimensions(TypedIndex shapeIndex, simBase simObj = null)
{
var dimensions = new ShapeDimensions();
if (!shapeIndex.Exists)
return dimensions;
dimensions.ShapeType = shapeIndex.Type;
try
{
// ✅ CASO ESPECIAL: Para simCurve, obtener dimensiones específicas de la curva
if (simObj is simCurve curve)
{
dimensions.InnerRadius = curve.InnerRadius;
dimensions.OuterRadius = curve.OuterRadius;
dimensions.StartAngle = curve.StartAngle;
dimensions.EndAngle = curve.EndAngle;
dimensions.ShapeType = -1; // Tipo especial para curvas
return dimensions;
}
// ✅ CASO ESPECIAL: Para simBotella, obtener dimensiones del cilindro visual
if (simObj is simBotella botella)
{
dimensions.Radius = botella.Radius;
dimensions.Height = botella.Height;
dimensions.ShapeType = -2; // Tipo especial para botellas (cilindro visual)
return dimensions;
}
// ✅ NUEVO: Para simTransporte, detectar si está en movimiento
if (simObj is simTransporte transporte)
{
dimensions.IsMoving = Math.Abs(transporte.Speed) > 0.001f; // Consideramos movimiento si la velocidad es mayor que 0.001
}
// Para otros objetos, usar la forma real de BEPU
if (shapeIndex.Type == BepuPhysics.Collidables.Sphere.Id)
{
var sphere = simulationManager.simulation.Shapes.GetShape<BepuPhysics.Collidables.Sphere>(shapeIndex.Index);
dimensions.Radius = sphere.Radius;
}
else if (shapeIndex.Type == BepuPhysics.Collidables.Box.Id)
{
var box = simulationManager.simulation.Shapes.GetShape<BepuPhysics.Collidables.Box>(shapeIndex.Index);
dimensions.Width = box.Width;
dimensions.Height = box.Height;
dimensions.Length = box.Length;
}
else if (shapeIndex.Type == BepuPhysics.Collidables.Cylinder.Id)
{
var cylinder = simulationManager.simulation.Shapes.GetShape<BepuPhysics.Collidables.Cylinder>(shapeIndex.Index);
dimensions.Radius = cylinder.Radius;
dimensions.Length = cylinder.Length; // En BEPU, Length es la altura del cilindro
}
else if (shapeIndex.Type == BepuPhysics.Collidables.Capsule.Id)
{
var capsule = simulationManager.simulation.Shapes.GetShape<BepuPhysics.Collidables.Capsule>(shapeIndex.Index);
dimensions.Radius = capsule.Radius;
dimensions.Length = capsule.Length;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] ERROR getting dimensions: {ex.Message}");
}
return dimensions;
}
private void RecreateVisualizationGeometry(simBase simObj, ModelVisual3D visual, ShapeDimensions dimensions)
{
if (visual?.Content is not GeometryModel3D geometryModel)
return;
try
{
MeshGeometry3D newGeometry = null;
Material newMaterial = null;
// Caso especial: simBotella usa esfera en BEPU pero se visualiza como cilindro
if (simObj is simBotella botella)
{
var meshBuilder = new MeshBuilder();
// Crear cilindro con tapas usando la versión mejorada
var p1 = new Point3D(0, 0, -botella.Height / 2);
var p2 = new Point3D(0, 0, botella.Height / 2);
meshBuilder.AddCylinder(p1, p2, radius: (double)botella.Radius, thetaDiv: 16, cap1: true, cap2: true);
newGeometry = meshBuilder.ToMesh();
newMaterial = GetMaterialForSimBase(simObj);
}
// Caso especial: simCurve necesita recreación completa de geometría
else if (simObj is simCurve curve)
{
var meshBuilder = new MeshBuilder();
// ✅ CORREGIDO: Respetar el flag de debug también en recreación
if (DebugShowIndividualTriangles)
{
CreateCurveDebugMeshWithIndividualTriangles(meshBuilder, curve);
newMaterial = GetDebugMaterialForCurve(curve);
}
else
{
CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
newMaterial = GetMaterialForSimBase(simObj);
}
newGeometry = meshBuilder.ToMesh();
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddSphere(new Point3D(0, 0, 0), dimensions.Radius);
newGeometry = meshBuilder.ToMesh();
newMaterial = GetMaterialForSimBase(simObj);
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Box.Id)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddBox(new Point3D(0, 0, 0), dimensions.Width, dimensions.Height, dimensions.Length);
newGeometry = meshBuilder.ToMesh();
newMaterial = GetMaterialForSimBase(simObj);
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Cylinder.Id)
{
var meshBuilder = new MeshBuilder();
// Crear cilindro con tapas usando la versión mejorada
var p1 = new Point3D(0, 0, -dimensions.Length / 2);
var p2 = new Point3D(0, 0, dimensions.Length / 2);
meshBuilder.AddCylinder(p1, p2, radius: (double)dimensions.Radius, thetaDiv: 16, cap1: true, cap2: true);
newGeometry = meshBuilder.ToMesh();
newMaterial = GetMaterialForSimBase(simObj);
}
if (newGeometry != null)
{
geometryModel.Geometry = newGeometry;
geometryModel.Material = newMaterial;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Recreate] ERROR recreating geometry: {ex.Message}");
}
}
public void Clear()
{
// ✅ NUEVO: Detener todas las animaciones activas antes de limpiar
foreach (var kvp in activeAnimations)
{
kvp.Value.Stop();
}
activeAnimations.Clear();
foreach (var model in simBaseToModelMap.Values)
{
viewport3D.Children.Remove(model);
}
simBaseToModelMap.Clear();
lastKnownDimensions.Clear();
}
/// <summary>
/// ✅ NUEVO: Material específico para debug de curvas
/// </summary>
/// <param name="curve">Curva en modo debug</param>
/// <returns>Material especial para visualización de debug</returns>
private Material GetDebugMaterialForCurve(simCurve curve)
{
// Material wireframe semi-transparente para debug
// Color naranja brillante para que sea muy visible
var debugBrush = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // Naranja
debugBrush.Opacity = 0.7; // 70% opacidad para ver superposiciones
return MaterialHelper.CreateMaterial(
debugBrush,
specularPower: 20, // Menos reflectante para mejor visibilidad
ambient: 250 // Más ambiente para que se vea bien en todas las condiciones
);
}
/// <summary>
/// ✅ NUEVO: Crea un material animable (no frozen) para poder animar sus propiedades
/// </summary>
/// <param name="color">Color del material</param>
/// <param name="specularPower">Potencia especular</param>
/// <param name="ambient">Nivel de luz ambiente</param>
/// <returns>Material que puede ser animado</returns>
private Material CreateAnimatableMaterial(Color color, double specularPower = 100, byte ambient = 190)
{
// Crear brush animable (no frozen)
var diffuseBrush = new SolidColorBrush(color);
// Crear material difuso
var diffuseMaterial = new DiffuseMaterial(diffuseBrush);
// Crear material especular - ✅ CORREGIDO: Usar un gris claro en lugar de blanco puro para reducir el brillo excesivo
var specularBrush = new SolidColorBrush(Color.FromRgb(128, 128, 128));
var specularMaterial = new SpecularMaterial(specularBrush, specularPower);
// Crear material "ambiente" (usando Emissive) - ✅ CORREGIDO: Usar un color mucho más oscuro para no sobreexponer
var ambientColor = Color.FromRgb((byte)(ambient / 4), (byte)(ambient / 4), (byte)(ambient / 4));
var ambientBrush = new SolidColorBrush(ambientColor);
var ambientMaterial = new EmissiveMaterial(ambientBrush);
// Combinar todos los materiales
var materialGroup = new MaterialGroup();
materialGroup.Children.Add(diffuseMaterial);
materialGroup.Children.Add(specularMaterial);
materialGroup.Children.Add(ambientMaterial);
// NO hacer freeze para mantener animable
return materialGroup;
}
/// <summary>
/// Función centralizada para obtener el material apropiado según el tipo de simBase
/// </summary>
/// <param name="simObj">Objeto de simulación para determinar el material</param>
/// <returns>Material configurado para el tipo de objeto</returns>
private Material GetMaterialForSimBase(simBase simObj)
{
if (simObj is simTransporte transporte)
{
// ✅ NUEVO: Cambiar color según si está en movimiento
bool isMoving = Math.Abs(transporte.Speed) > 0.001f;
if (isMoving)
{
// ✅ CORREGIDO: Usar material animable para transportes en movimiento
return CreateAnimatableMaterial(Color.FromRgb(40, 255, 40), 180, 240);
}
else
{
// Material plástico verde normal para transportes detenidos
return MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
specularPower: 140,
ambient: 200
);
}
}
else if (simObj is simGuia)
{
// Material semitransparente gris oscuro para guías
var guiaBrush = new SolidColorBrush(Color.FromRgb(80, 80, 80));
guiaBrush.Opacity = 0.4; // 40% opacidad para mejor visualización semitransparente
return MaterialHelper.CreateMaterial(
guiaBrush,
specularPower: 30,
ambient: 100
);
}
else if (simObj is simBarrera)
{
// ✅ MEJORADO: Material para haz de luz más realista
var lightBeamBrush = new SolidColorBrush(Color.FromRgb(255, 255, 100)); // Amarillo cálido
lightBeamBrush.Opacity = 0.6; // 60% opacidad para simular haz de luz visible
return MaterialHelper.CreateMaterial(
lightBeamBrush,
specularPower: 80, // Algo de brillo para simular luz
ambient: 200 // Ambiente alto para que "emita" luz
);
}
else if (simObj is simBotella)
{
// Material plástico brillante rojo para botellas
return MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(255, 80, 80)),
specularPower: 120,
ambient: 200
);
}
else if (simObj is simDescarte)
{
// Material semi-transparente magenta para descartes (85% transparente)
var descarteBrush = new SolidColorBrush(Color.FromRgb(255, 100, 255));
descarteBrush.Opacity = 0.15; // 15% opacidad = 85% transparencia
return MaterialHelper.CreateMaterial(
descarteBrush,
specularPower: 60,
ambient: 120
);
}
else if (simObj is simCurve)
{
// Material plástico brillante para curvas (azul verdoso)
return MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 150, 200)),
specularPower: 130,
ambient: 190
);
}
else
{
// Material plástico gris estándar para objetos no identificados
return MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(128, 128, 128)),
specularPower: 100,
ambient: 190
);
}
}
public void SetCameraView(CameraView view)
{
if (viewport3D?.Camera is not PerspectiveCamera camera) return;
switch (view)
{
case CameraView.Top:
camera.Position = new Point3D(0, 0, 10);
camera.LookDirection = new Vector3D(0, 0, -1);
camera.UpDirection = new Vector3D(0, 1, 0);
break;
case CameraView.Side:
camera.Position = new Point3D(10, 0, 0);
camera.LookDirection = new Vector3D(-1, 0, 0);
camera.UpDirection = new Vector3D(0, 0, 1);
break;
case CameraView.Front:
camera.Position = new Point3D(0, 10, 0);
camera.LookDirection = new Vector3D(0, -1, 0);
camera.UpDirection = new Vector3D(0, 0, 1);
break;
case CameraView.Isometric:
camera.Position = new Point3D(7, 7, 7);
camera.LookDirection = new Vector3D(-1, -1, -1);
camera.UpDirection = new Vector3D(0, 0, 1);
break;
}
}
/// <summary>
/// ✅ NUEVO: Crea una animación de movimiento para transportes usando StoryBoard
/// Combina rotación sutil + pulsación de brillo para simular movimiento
/// </summary>
/// <param name="transporte">Transporte al cual aplicar la animación</param>
/// <param name="visual">Visual3D del transporte</param>
private void CreateTransportMovementAnimation(simTransporte transporte, ModelVisual3D visual)
{
try
{
// Detener animación anterior si existe
StopTransportAnimation(transporte);
// Crear nuevo StoryBoard
var storyboard = new Storyboard();
storyboard.RepeatBehavior = RepeatBehavior.Forever;
// ✅ ANIMACIÓN 1: Rotación sutil continua alrededor del eje Z (muy lenta)
var rotationAnimation = new DoubleAnimation
{
From = 0,
To = 360,
Duration = TimeSpan.FromSeconds(20), // 20 segundos por vuelta completa (muy lento)
RepeatBehavior = RepeatBehavior.Forever
};
// Crear transform group si no existe
if (visual.Transform is not Transform3DGroup transformGroup)
{
transformGroup = new Transform3DGroup();
if (visual.Transform != null)
transformGroup.Children.Add(visual.Transform);
visual.Transform = transformGroup;
}
// Agregar rotación de animación
var animationRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0));
transformGroup.Children.Add(animationRotation);
// Configurar target para la rotación
Storyboard.SetTarget(rotationAnimation, animationRotation);
Storyboard.SetTargetProperty(rotationAnimation, new PropertyPath("Rotation.Angle"));
storyboard.Children.Add(rotationAnimation);
// ✅ ANIMACIÓN 2: Pulsación de brillo del material
if (visual.Content is GeometryModel3D geometryModel &&
geometryModel.Material is MaterialGroup materialGroup)
{
// Buscar el material difuso para animar (debe ser el primero en mi CreateAnimatableMaterial)
var diffuseMaterial = materialGroup.Children.OfType<DiffuseMaterial>().FirstOrDefault();
if (diffuseMaterial?.Brush is SolidColorBrush diffuseBrush)
{
// ✅ CORREGIDO: El brush ya debe ser animable si se creó con CreateAnimatableMaterial
var colorAnimation = new ColorAnimation
{
From = Color.FromRgb(40, 255, 40), // Verde brillante
To = Color.FromRgb(80, 255, 80), // Verde más brillante
Duration = TimeSpan.FromSeconds(1.5), // Pulsación cada 1.5 segundos
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTarget(colorAnimation, diffuseBrush);
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("Color"));
storyboard.Children.Add(colorAnimation);
}
}
// Iniciar animación
storyboard.Begin();
// Almacenar referencia para poder detenerla después
activeAnimations[transporte] = storyboard;
System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de movimiento creada para transporte");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error creando animación: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Detiene la animación de movimiento de un transporte
/// </summary>
/// <param name="transporte">Transporte cuya animación se desea detener</param>
private void StopTransportAnimation(simTransporte transporte)
{
try
{
if (activeAnimations.TryGetValue(transporte, out var storyboard))
{
storyboard.Stop();
activeAnimations.Remove(transporte);
// Limpiar transforms de animación del visual
if (simBaseToModelMap.TryGetValue(transporte, out var visual) &&
visual.Transform is Transform3DGroup transformGroup)
{
// Remover solo la rotación de animación (la última agregada)
if (transformGroup.Children.Count > 1)
{
transformGroup.Children.RemoveAt(transformGroup.Children.Count - 1);
}
}
System.Diagnostics.Debug.WriteLine($"[3D Animation] ✅ Animación de transporte detenida");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error deteniendo animación: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Gestiona automáticamente las animaciones de transportes según su estado
/// </summary>
/// <param name="transporte">Transporte a gestionar</param>
/// <param name="visual">Visual3D del transporte</param>
private void ManageTransportAnimation(simTransporte transporte, ModelVisual3D visual)
{
try
{
bool isMoving = Math.Abs(transporte.Speed) > 0.001f;
bool hasActiveAnimation = activeAnimations.ContainsKey(transporte);
if (isMoving && !hasActiveAnimation)
{
// Crear animación si el transporte está en movimiento y no tiene animación
CreateTransportMovementAnimation(transporte, visual);
}
else if (!isMoving && hasActiveAnimation)
{
// Detener animación si el transporte está detenido y tiene animación
StopTransportAnimation(transporte);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Animation] ❌ Error gestionando animación: {ex.Message}");
}
}
/// <summary>
/// ✅ MODO DEBUG DE TRIÁNGULOS DE CURVAS:
///
/// TRUE (Debug Mode): Muestra triángulos individuales reales de BEPU con bordes visibles
/// FALSE (Normal Mode): Muestra superficie continua suave
///
/// Uso desde código:
/// // Activar modo debug para ver triángulos reales
/// visualizationManager.ShowRealTriangles();
///
/// // Desactivar para ver superficie continua
/// visualizationManager.ShowContinuousSurface();
/// </summary>
/// <param name="enableDebug">True para mostrar triángulos reales, false para superficie continua</param>
/// <param name="forceRefresh">True para forzar actualización inmediata de todas las curvas</param>
public void SetDebugTrianglesMode(bool enableDebug, bool forceRefresh = true)
{
bool wasChanged = DebugShowIndividualTriangles != enableDebug;
DebugShowIndividualTriangles = enableDebug;
System.Diagnostics.Debug.WriteLine($"[3D Debug] Modo triángulos reales: {(enableDebug ? "ACTIVADO (triángulos individuales)" : "DESACTIVADO (superficie continua)")}");
if (wasChanged && forceRefresh)
{
RefreshCurveVisualizations();
}
}
/// <summary>
/// ✅ NUEVO: Método simple para activar el modo debug - muestra triángulos reales planos
/// </summary>
public void ShowRealTriangles()
{
SetDebugTrianglesMode(true);
}
/// <summary>
/// ✅ NUEVO: Método simple para activar el modo superficie continua
/// </summary>
public void ShowContinuousSurface()
{
SetDebugTrianglesMode(false);
}
/// <summary>
/// ✅ NUEVO: Fuerza la regeneración de todas las visualizaciones de curvas
/// Útil cuando se cambia el modo debug
/// </summary>
public void RefreshCurveVisualizations()
{
if (simulationManager?.Cuerpos == null)
return;
try
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshing curve visualizations...");
// Encontrar todas las curvas y forzar su regeneración
var curvesToRefresh = simulationManager.Cuerpos.OfType<simCurve>().ToList();
foreach (var curve in curvesToRefresh)
{
// Remover las dimensiones conocidas para forzar recreación
lastKnownDimensions.Remove(curve);
// Forzar actualización de la visualización
if (simBaseToModelMap.ContainsKey(curve))
{
UpdateVisualizationFromSimBase(curve);
}
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] Refreshed {curvesToRefresh.Count} curve visualizations");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR refreshing curve visualizations: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Obtiene el estado actual del modo debug
/// </summary>
/// <returns>True si el modo debug está activo</returns>
public bool IsDebugTrianglesModeEnabled()
{
return DebugShowIndividualTriangles;
}
/// <summary>
/// ✅ CORREGIDO: Activa la visualización de triángulos locales de BEPU
/// Muestra los triángulos exactos extraídos de la simulación física en coordenadas locales
/// </summary>
/// <param name="enable">True para mostrar triángulos locales, false para superficie normal</param>
public void SetRealBEPUTrianglesMode(bool enable)
{
SetDebugTrianglesMode(enable);
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Modo triángulos locales: {(enable ? "ACTIVADO" : "DESACTIVADO")}");
}
/// <summary>
/// ✅ CORREGIDO: Verifica si una curva tiene triángulos válidos en BEPU
/// </summary>
/// <param name="curve">Curva a verificar</param>
/// <returns>True si tiene triángulos válidos</returns>
public bool HasValidBEPUTriangles(simCurve curve)
{
if (curve == null) return false;
try
{
var triangles = curve.GetRealBEPUTriangles(); // Obtiene triángulos locales
bool hasTriangles = triangles.Length > 0;
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Curva tiene {triangles.Length} triángulos válidos: {hasTriangles}");
return hasTriangles;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D BEPU] Error verificando triángulos: {ex.Message}");
return false;
}
}
/// <summary>
/// Carga el estado guardado de la cámara desde EstadoPersistente
/// </summary>
private void LoadCameraState()
{
try
{
var cameraSettings = EstadoPersistente.Instance.Camera;
viewport3D.Camera = new PerspectiveCamera
{
Position = new Point3D(cameraSettings.PositionX, cameraSettings.PositionY, cameraSettings.PositionZ),
LookDirection = new Vector3D(cameraSettings.LookDirectionX, cameraSettings.LookDirectionY, cameraSettings.LookDirectionZ),
UpDirection = new Vector3D(cameraSettings.UpDirectionX, cameraSettings.UpDirectionY, cameraSettings.UpDirectionZ),
FieldOfView = cameraSettings.FieldOfView
};
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR loading camera state: {ex.Message}");
// Fallback a configuración por defecto
viewport3D.Camera = new PerspectiveCamera
{
LookDirection = new Vector3D(-3.86, 18.1, -10),
UpDirection = new Vector3D(-0.1, 0.48, 0.87),
Position = new Point3D(3.86, -18.13, 10),
FieldOfView = 60
};
}
}
/// <summary>
/// Guarda el estado actual de la cámara en EstadoPersistente
/// </summary>
private void SaveCameraState()
{
try
{
if (viewport3D?.Camera is PerspectiveCamera camera)
{
var cameraSettings = EstadoPersistente.Instance.Camera;
cameraSettings.PositionX = camera.Position.X;
cameraSettings.PositionY = camera.Position.Y;
cameraSettings.PositionZ = camera.Position.Z;
cameraSettings.LookDirectionX = camera.LookDirection.X;
cameraSettings.LookDirectionY = camera.LookDirection.Y;
cameraSettings.LookDirectionZ = camera.LookDirection.Z;
cameraSettings.UpDirectionX = camera.UpDirection.X;
cameraSettings.UpDirectionY = camera.UpDirection.Y;
cameraSettings.UpDirectionZ = camera.UpDirection.Z;
cameraSettings.FieldOfView = camera.FieldOfView;
EstadoPersistente.Instance.GuardarEstado();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR saving camera state: {ex.Message}");
}
}
/// <summary>
/// Se suscribe a eventos del viewport para detectar cambios en la cámara
/// </summary>
private void SubscribeToCameraEvents()
{
try
{
// Suscribirse a eventos del viewport para detectar cambios en la cámara
viewport3D.CameraChanged += (sender, e) => SaveCameraState();
// También suscribirse a eventos de mouse para detectar interacciones
viewport3D.MouseUp += (sender, e) => SaveCameraState();
viewport3D.MouseWheel += (sender, e) => SaveCameraState();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR subscribing to camera events: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Crea la visualización del haz de luz para simBarrera
/// Muestra una línea semi-transparente que representa el RayCast
/// </summary>
/// <param name="barrera">Barrera que usa RayCast para detección</param>
private void CreateBarreraVisualization(simBarrera barrera)
{
try
{
var visual3D = CreateLightBeamVisualization(barrera);
if (visual3D != null)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Visual3D creado, actualizando transformación...");
// Posicionar y orientar el haz de luz
UpdateBarreraTransform(barrera, visual3D);
// Agregar a la vista 3D y asociar con simBase
viewport3D.Children.Add(visual3D);
simBaseToModelMap[barrera] = visual3D;
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Haz de luz creado exitosamente y agregado al viewport");
System.Diagnostics.Debug.WriteLine($" Total children en viewport: {viewport3D.Children.Count}");
}
else
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ CreateLightBeamVisualization devolvió null");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR creando visualización: {ex.Message}");
System.Diagnostics.Debug.WriteLine($" Stack trace: {ex.StackTrace}");
}
}
/// <summary>
/// ✅ NUEVO: Actualiza la visualización existente de simBarrera
/// </summary>
/// <param name="barrera">Barrera a actualizar</param>
private void UpdateBarreraVisualization(simBarrera barrera)
{
try
{
if (!simBaseToModelMap.TryGetValue(barrera, out var visual))
return;
// Verificar si las dimensiones han cambiado
var currentDimensions = GetBarreraDimensions(barrera);
bool dimensionsChanged = false;
if (lastKnownDimensions.TryGetValue(barrera, out var lastDimensions))
{
dimensionsChanged = !currentDimensions.Equals(lastDimensions);
}
else
{
dimensionsChanged = true; // Primera vez
}
// Si las dimensiones cambiaron, recrear la geometría
if (dimensionsChanged)
{
RecreateBarreraGeometry(barrera, visual, currentDimensions);
lastKnownDimensions[barrera] = currentDimensions;
}
// Actualizar transformación (posición y rotación)
UpdateBarreraTransform(barrera, visual);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR actualizando visualización: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Crea la geometría del haz de luz (línea semi-transparente)
/// </summary>
/// <param name="barrera">Barrera para crear el haz</param>
/// <returns>Modelo visual del haz de luz</returns>
private ModelVisual3D CreateLightBeamVisualization(simBarrera barrera)
{
try
{
var meshBuilder = new MeshBuilder();
// Crear una línea delgada que representa el haz de luz
var halfWidth = barrera.Width / 2f;
var beamThickness = 0.005f; // 5mm de grosor para el haz
var beamHeight = 0.02f; // 2cm de altura para que sea visible
// Puntos del haz de luz (de extremo a extremo)
var startPoint = new Point3D(-halfWidth, 0, beamHeight / 2);
var endPoint = new Point3D(halfWidth, 0, beamHeight / 2);
// Crear cilindro delgado para el haz principal
meshBuilder.AddCylinder(startPoint, endPoint, beamThickness, 8, false, false);
// ✅ OPCIONAL: Agregar pequeñas esferas en los extremos para marcar emisor y receptor
meshBuilder.AddSphere(new Point3D(-halfWidth, 0, 2 * beamThickness), beamThickness * 2, 6, 6); // Emisor
//meshBuilder.AddSphere(new Point3D(halfWidth, 0, 2 * beamThickness), beamThickness * 2, 6, 6); // Receptor
// Material semi-transparente para simular haz de luz
var material = GetMaterialForSimBase(barrera);
var model = new GeometryModel3D(meshBuilder.ToMesh(), material);
var visual = new ModelVisual3D { Content = model };
return visual;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR creando geometría del haz: {ex.Message}");
return null;
}
}
/// <summary>
/// ✅ NUEVO: Actualiza la transformación (posición y rotación) de la barrera
/// </summary>
/// <param name="barrera">Barrera a transformar</param>
/// <param name="visual">Visual 3D a transformar</param>
private void UpdateBarreraTransform(simBarrera barrera, ModelVisual3D visual)
{
try
{
var transform = new Transform3DGroup();
// ✅ CORREGIDO: rotationZ ya está en grados WPF, no necesita conversión adicional
transform.Children.Add(new RotateTransform3D(
new AxisAngleRotation3D(new Vector3D(0, 0, 1), barrera.AngleRadians)));
// Traslación
transform.Children.Add(new TranslateTransform3D(barrera.Position.X, barrera.Position.Y, barrera.Position.Z));
visual.Transform = transform;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR actualizando transformación: {ex.Message}");
}
}
/// <summary>
/// ✅ NUEVO: Obtiene las dimensiones de una barrera para detectar cambios
/// </summary>
/// <param name="barrera">Barrera de la cual obtener dimensiones</param>
/// <returns>Dimensiones de la barrera</returns>
private ShapeDimensions GetBarreraDimensions(simBarrera barrera)
{
return new ShapeDimensions
{
Width = barrera.Width,
Height = barrera.Height,
Length = 0,
Radius = 0,
ShapeType = -3, // Tipo especial para barreras
InnerRadius = 0,
OuterRadius = 0,
StartAngle = 0,
EndAngle = 0,
IsMoving = false // Las barreras no se mueven
};
}
/// <summary>
/// ✅ NUEVO: Recrea la geometría de la barrera cuando cambian las dimensiones
/// </summary>
/// <param name="barrera">Barrera a recrear</param>
/// <param name="visual">Visual 3D a actualizar</param>
/// <param name="dimensions">Nuevas dimensiones</param>
private void RecreateBarreraGeometry(simBarrera barrera, ModelVisual3D visual, ShapeDimensions dimensions)
{
if (visual?.Content is not GeometryModel3D geometryModel)
return;
try
{
var meshBuilder = new MeshBuilder();
// Recrear la geometría del haz con las nuevas dimensiones
var halfWidth = dimensions.Width / 2f;
var beamThickness = 0.005f;
var beamHeight = 0.02f;
var startPoint = new Point3D(-halfWidth, 0, beamHeight / 2);
var endPoint = new Point3D(halfWidth, 0, beamHeight / 2);
meshBuilder.AddCylinder(startPoint, endPoint, beamThickness, 8, false, false);
meshBuilder.AddSphere(new Point3D(-halfWidth, 0, 2*beamThickness), beamThickness * 2, 6, 6);
//meshBuilder.AddSphere(new Point3D(halfWidth, 0, 2*beamThickness), beamThickness * 2, 6, 6);
var newGeometry = meshBuilder.ToMesh();
var newMaterial = GetMaterialForSimBase(barrera);
geometryModel.Geometry = newGeometry;
geometryModel.Material = newMaterial;
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ✅ Geometría recreada con ancho: {dimensions.Width}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Barrera] ❌ ERROR recreando geometría: {ex.Message}");
}
}
}
public enum CameraView
{
Top,
Side,
Front,
Isometric
}
}