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; namespace CtrEditor.Simulacion { /// /// Estructura para almacenar las dimensiones de las formas para detectar cambios /// 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; } 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; } } /// /// 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 /// public class BEPUVisualization3DManager { private HelixViewport3D viewport3D; private SimulationManagerBEPU simulationManager; private Dictionary simBaseToModelMap; private Dictionary lastKnownDimensions; // ✅ CORREGIDO: Flag de debug para mostrar triángulos individuales de curvas (true temporalmente para verificar) public static bool DebugShowIndividualTriangles { get; set; } = true; 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(); lastKnownDimensions = new Dictionary(); 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 = Colors.White, Direction = new Vector3D(0, 0, -1) }; viewport3D.Children.Add(new ModelVisual3D { Content = directionalLight }); var ambientLight = new AmbientLight { Color = Color.FromRgb(64, 64, 64) }; 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}"); } } /// /// Sincroniza usando los objetos simBase del SimulationManager como fuente de verdad /// Esto garantiza que cada objeto único tiene su propia visualización /// 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(); 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) { 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 { // ✅ Usar directamente los triángulos originales de BEPU (superficie continua) 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; } } /// /// ✅ CORREGIDO: Crea mesh desde los triángulos locales de BEPU /// Extrae la geometría exacta en coordenadas locales para evitar transformación duplicada /// private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve) { try { // ✅ EXTRAER TRIÁNGULOS LOCALES DE BEPU (sin transformación duplicada) var localTriangles = curve.GetRealBEPUTriangles(); if (localTriangles.Length == 0) { System.Diagnostics.Debug.WriteLine($"[3D BEPU] WARNING: No se pudieron extraer triángulos locales, usando fallback"); CreateCurveMeshFallback(meshBuilder, curve); return; } System.Diagnostics.Debug.WriteLine($"[3D BEPU] Creando mesh desde {localTriangles.Length} triángulos locales de BEPU"); // ✅ USAR TRIÁNGULOS LOCALES DE BEPU (la transformación se aplicará automáticamente en UpdateVisualization) foreach (var triangle in localTriangles) { // Convertir triángulos de BEPU a puntos 3D de Helix 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 en coordenadas locales al mesh meshBuilder.AddTriangle(pointA, pointB, pointC); } System.Diagnostics.Debug.WriteLine($"[3D BEPU] ✅ Mesh creado usando triángulos locales de BEPU (transformación aplicada automáticamente)"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[3D BEPU] ERROR extrayendo triángulos locales: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"[3D BEPU] Fallback a geometría recreada"); CreateCurveMeshFallback(meshBuilder, curve); } } /// /// ✅ 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) /// 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"); CreateCurveMeshFallback(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); } } /// /// ✅ NUEVO: Agrega bordes debug a un triángulo individual /// 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}"); } } /// /// ✅ NUEVO: Agrega un borde delgado entre dos puntos para debug visual /// 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}"); } } /// /// ✅ NUEVO: Geometría básica de debug cuando fallan otras opciones /// 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}"); } } /// /// Método fallback que recrea la geometría (mantener por compatibilidad) /// private void CreateCurveMeshFallback(MeshBuilder meshBuilder, simCurve curve) { // Obtener parámetros de la curva float innerRadius = curve.InnerRadius; float outerRadius = curve.OuterRadius; float startAngle = curve.StartAngle; float endAngle = curve.EndAngle; System.Diagnostics.Debug.WriteLine($"[3D Fallback] Parameters - Inner: {innerRadius}, Outer: {outerRadius}, Start: {startAngle}, End: {endAngle}"); // Configuración de segmentos const float SegmentationFactor = 32f / 3f; const int MinSegments = 8; const int MaxSegments = 64; // Calcular número de segmentos basado en el tamaño del arco float arcLength = (endAngle - startAngle) * ((innerRadius + outerRadius) / 2f); int segments = (int)(arcLength * SegmentationFactor); segments = Math.Max(MinSegments, Math.Min(segments, MaxSegments)); float angleStep = (endAngle - startAngle) / segments; // Altura muy pequeña para simular triángulos planos const float curveHeight = 0.05f; // Generar vértices para el arco interior y exterior var innerBottomPoints = new Point3D[segments + 1]; var innerTopPoints = new Point3D[segments + 1]; var outerBottomPoints = new Point3D[segments + 1]; var outerTopPoints = new Point3D[segments + 1]; for (int i = 0; i <= segments; i++) { float angle = startAngle + i * angleStep; float cosAngle = (float)Math.Cos(angle); float sinAngle = (float)Math.Sin(angle); // Puntos en la parte inferior (Z = 0) innerBottomPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, 0); outerBottomPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, 0); // Puntos en la parte superior (Z = curveHeight) innerTopPoints[i] = new Point3D(innerRadius * cosAngle, innerRadius * sinAngle, curveHeight); outerTopPoints[i] = new Point3D(outerRadius * cosAngle, outerRadius * sinAngle, curveHeight); } // Crear la superficie superior de la curva for (int i = 0; i < segments; i++) { meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i], outerTopPoints[i + 1]); meshBuilder.AddTriangle(innerTopPoints[i], outerTopPoints[i + 1], innerTopPoints[i + 1]); } // Crear la superficie inferior de la curva for (int i = 0; i < segments; i++) { meshBuilder.AddTriangle(innerBottomPoints[i], outerBottomPoints[i + 1], outerBottomPoints[i]); meshBuilder.AddTriangle(innerBottomPoints[i], innerBottomPoints[i + 1], outerBottomPoints[i + 1]); } // Crear las paredes laterales for (int i = 0; i < segments; i++) { meshBuilder.AddQuad(innerBottomPoints[i], innerBottomPoints[i + 1], innerTopPoints[i + 1], innerTopPoints[i]); meshBuilder.AddQuad(outerBottomPoints[i], outerTopPoints[i], outerTopPoints[i + 1], outerBottomPoints[i + 1]); } // Crear las paredes de los extremos meshBuilder.AddQuad(innerBottomPoints[0], innerTopPoints[0], outerTopPoints[0], outerBottomPoints[0]); meshBuilder.AddQuad(outerBottomPoints[segments], outerTopPoints[segments], innerTopPoints[segments], innerBottomPoints[segments]); } 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}"); } // 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; } // Para otros objetos, usar la forma real de BEPU if (shapeIndex.Type == BepuPhysics.Collidables.Sphere.Id) { var sphere = simulationManager.simulation.Shapes.GetShape(shapeIndex.Index); dimensions.Radius = sphere.Radius; } else if (shapeIndex.Type == BepuPhysics.Collidables.Box.Id) { var box = simulationManager.simulation.Shapes.GetShape(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(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(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() { foreach (var model in simBaseToModelMap.Values) { viewport3D.Children.Remove(model); } simBaseToModelMap.Clear(); lastKnownDimensions.Clear(); } /// /// ✅ NUEVO: Material específico para debug de curvas /// /// Curva en modo debug /// Material especial para visualización de debug 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 ); } /// /// Función centralizada para obtener el material apropiado según el tipo de simBase /// /// Objeto de simulación para determinar el material /// Material configurado para el tipo de objeto private Material GetMaterialForSimBase(simBase simObj) { if (simObj is simTransporte) { // Material plástico verde para transportes 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; } } /// /// ✅ 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(); /// /// True para mostrar triángulos reales, false para superficie continua /// True para forzar actualización inmediata de todas las curvas 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(); } } /// /// ✅ NUEVO: Método simple para activar el modo debug - muestra triángulos reales planos /// public void ShowRealTriangles() { SetDebugTrianglesMode(true); } /// /// ✅ NUEVO: Método simple para activar el modo superficie continua /// public void ShowContinuousSurface() { SetDebugTrianglesMode(false); } /// /// ✅ NUEVO: Fuerza la regeneración de todas las visualizaciones de curvas /// Útil cuando se cambia el modo debug /// 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().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}"); } } /// /// ✅ NUEVO: Obtiene el estado actual del modo debug /// /// True si el modo debug está activo public bool IsDebugTrianglesModeEnabled() { return DebugShowIndividualTriangles; } /// /// ✅ 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 /// /// True para mostrar triángulos locales, false para superficie normal public void SetRealBEPUTrianglesMode(bool enable) { SetDebugTrianglesMode(enable); System.Diagnostics.Debug.WriteLine($"[3D BEPU] Modo triángulos locales: {(enable ? "ACTIVADO" : "DESACTIVADO")}"); } /// /// ✅ CORREGIDO: Verifica si una curva tiene triángulos válidos en BEPU /// /// Curva a verificar /// True si tiene triángulos válidos 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; } } /// /// Carga el estado guardado de la cámara desde EstadoPersistente /// 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 }; } } /// /// Guarda el estado actual de la cámara en EstadoPersistente /// 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}"); } } /// /// Se suscribe a eventos del viewport para detectar cambios en la cámara /// 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}"); } } /// /// ✅ NUEVO: Crea la visualización del haz de luz para simBarrera /// Muestra una línea semi-transparente que representa el RayCast /// /// Barrera que usa RayCast para detección 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}"); } } /// /// ✅ NUEVO: Actualiza la visualización existente de simBarrera /// /// Barrera a actualizar 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}"); } } /// /// ✅ NUEVO: Crea la geometría del haz de luz (línea semi-transparente) /// /// Barrera para crear el haz /// Modelo visual del haz de luz 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; } } /// /// ✅ NUEVO: Actualiza la transformación (posición y rotación) de la barrera /// /// Barrera a transformar /// Visual 3D a transformar 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}"); } } /// /// ✅ NUEVO: Obtiene las dimensiones de una barrera para detectar cambios /// /// Barrera de la cual obtener dimensiones /// Dimensiones de la barrera 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 }; } /// /// ✅ NUEVO: Recrea la geometría de la barrera cuando cambian las dimensiones /// /// Barrera a recrear /// Visual 3D a actualizar /// Nuevas dimensiones 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 } }