1215 lines
52 KiB
C#
1215 lines
52 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;
|
|
|
|
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; }
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <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: Flag de debug para mostrar triángulos individuales de curvas
|
|
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<simBase, ModelVisual3D>();
|
|
lastKnownDimensions = new Dictionary<simBase, ShapeDimensions>();
|
|
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
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;
|
|
|
|
// Verificar si el objeto aún está en la lista de cuerpos activos
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Crea mesh directamente desde los triángulos de BEPU (debug real)
|
|
/// </summary>
|
|
private void CreateCurveMeshFromBEPUTriangles(MeshBuilder meshBuilder, simCurve curve)
|
|
{
|
|
try
|
|
{
|
|
// Obtener los triángulos originales directamente de BEPU
|
|
var originalTriangles = curve.GetOriginalTriangles();
|
|
|
|
if (originalTriangles.Count == 0)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
|
|
return;
|
|
}
|
|
|
|
// Altura para simular triángulos planos (misma que en BEPU)
|
|
const float curveHeight = 0.05f; // simCurve.zAltura_Curve
|
|
|
|
// Convertir cada triángulo de BEPU a triángulos en Helix
|
|
foreach (var triangle in originalTriangles)
|
|
{
|
|
if (triangle.Count != 3)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle with {triangle.Count} vertices");
|
|
continue;
|
|
}
|
|
|
|
// Convertir Vector3 de BEPU a Point3D de Helix
|
|
// Los triángulos en BEPU están en el plano XY (Z=0), crear superficie 3D
|
|
|
|
// Puntos de la superficie inferior (Z = 0)
|
|
var p1Bottom = new Point3D(triangle[0].X, triangle[0].Y, 0);
|
|
var p2Bottom = new Point3D(triangle[1].X, triangle[1].Y, 0);
|
|
var p3Bottom = new Point3D(triangle[2].X, triangle[2].Y, 0);
|
|
|
|
// Puntos de la superficie superior (Z = curveHeight)
|
|
var p1Top = new Point3D(triangle[0].X, triangle[0].Y, curveHeight);
|
|
var p2Top = new Point3D(triangle[1].X, triangle[1].Y, curveHeight);
|
|
var p3Top = new Point3D(triangle[2].X, triangle[2].Y, curveHeight);
|
|
|
|
// Crear superficie superior del triángulo
|
|
meshBuilder.AddTriangle(p1Top, p2Top, p3Top);
|
|
|
|
// Crear superficie inferior del triángulo (orden inverso para normales correctas)
|
|
meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom);
|
|
|
|
// Crear paredes laterales del triángulo (3 quads)
|
|
meshBuilder.AddQuad(p1Bottom, p2Bottom, p2Top, p1Top); // Lado 1-2
|
|
meshBuilder.AddQuad(p2Bottom, p3Bottom, p3Top, p2Top); // Lado 2-3
|
|
meshBuilder.AddQuad(p3Bottom, p1Bottom, p1Top, p3Top); // Lado 3-1
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating mesh from BEPU triangles: {ex.Message}");
|
|
|
|
// Fallback: usar el método anterior si falla la lectura de BEPU
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to recreated geometry");
|
|
CreateCurveMeshFallback(meshBuilder, curve);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ✅ NUEVO: Función de debug que muestra triángulos individuales de BEPU
|
|
/// Cada triángulo se renderiza de manera separada para poder hacer debug visual
|
|
/// </summary>
|
|
private void CreateCurveDebugMeshWithIndividualTriangles(MeshBuilder meshBuilder, simCurve curve)
|
|
{
|
|
try
|
|
{
|
|
// Obtener los triángulos originales directamente de BEPU
|
|
var originalTriangles = curve.GetOriginalTriangles();
|
|
|
|
if (originalTriangles.Count == 0)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: No triangles found in BEPU curve");
|
|
return;
|
|
}
|
|
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating debug visualization for {originalTriangles.Count} triangles");
|
|
|
|
// Altura muy pequeña para mostrar triángulos planos (como en BEPU)
|
|
const float debugHeight = 0.02f; // Más bajo que la superficie normal para diferenciarlo
|
|
const float triangleSeparation = 0.01f; // Separación pequeña entre triángulos para distinguirlos
|
|
|
|
// Convertir cada triángulo de BEPU a un triángulo 3D individual
|
|
for (int i = 0; i < originalTriangles.Count; i++)
|
|
{
|
|
var triangle = originalTriangles[i];
|
|
|
|
if (triangle.Count != 3)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] WARNING: Invalid triangle {i} with {triangle.Count} vertices");
|
|
continue;
|
|
}
|
|
|
|
// Calcular el centro del triángulo para aplicar separación
|
|
var center = new System.Numerics.Vector3(
|
|
(triangle[0].X + triangle[1].X + triangle[2].X) / 3f,
|
|
(triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3f,
|
|
0
|
|
);
|
|
|
|
// Aplicar una pequeña separación desde el centro para distinguir triángulos
|
|
var offset = center * triangleSeparation;
|
|
|
|
// Crear triángulo superior (visible desde arriba)
|
|
var p1Top = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, debugHeight);
|
|
var p2Top = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, debugHeight);
|
|
var p3Top = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, debugHeight);
|
|
|
|
// Crear triángulo inferior (visible desde abajo)
|
|
var p1Bottom = new Point3D(triangle[0].X + offset.X, triangle[0].Y + offset.Y, 0);
|
|
var p2Bottom = new Point3D(triangle[1].X + offset.X, triangle[1].Y + offset.Y, 0);
|
|
var p3Bottom = new Point3D(triangle[2].X + offset.X, triangle[2].Y + offset.Y, 0);
|
|
|
|
// Agregar triángulo superior (normal hacia arriba)
|
|
meshBuilder.AddTriangle(p1Top, p2Top, p3Top);
|
|
|
|
// Agregar triángulo inferior (normal hacia abajo)
|
|
meshBuilder.AddTriangle(p1Bottom, p3Bottom, p2Bottom);
|
|
|
|
// ✅ OPCIONAL: Agregar bordes/wireframe para mejor visualización del debug
|
|
// Crear líneas delgadas en los bordes del triángulo para verlo mejor
|
|
const float edgeThickness = 0.005f;
|
|
|
|
// Borde 1-2
|
|
AddDebugEdge(meshBuilder, p1Top, p2Top, p1Bottom, p2Bottom, edgeThickness);
|
|
// Borde 2-3
|
|
AddDebugEdge(meshBuilder, p2Top, p3Top, p2Bottom, p3Bottom, edgeThickness);
|
|
// Borde 3-1
|
|
AddDebugEdge(meshBuilder, p3Top, p1Top, p3Bottom, p1Bottom, edgeThickness);
|
|
}
|
|
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created debug mesh with {originalTriangles.Count} individual triangles");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] ERROR creating debug mesh: {ex.Message}");
|
|
|
|
// Fallback: mostrar mensaje de error en consola y usar geometría básica
|
|
System.Diagnostics.Debug.WriteLine($"[3D Debug] Falling back to basic debug geometry");
|
|
CreateBasicDebugGeometry(meshBuilder, curve);
|
|
}
|
|
}
|
|
|
|
/// <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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Método fallback que recrea la geometría (mantener por compatibilidad)
|
|
/// </summary>
|
|
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<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()
|
|
{
|
|
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>
|
|
/// 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)
|
|
{
|
|
// 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)
|
|
{
|
|
// Material semi-transparente amarillo para barreras (haz de luz)
|
|
var yellowBrush = new SolidColorBrush(Color.FromRgb(255, 255, 0));
|
|
yellowBrush.Opacity = 0.3; // 30% opacidad para simular haz de luz
|
|
return MaterialHelper.CreateMaterial(
|
|
yellowBrush,
|
|
specularPower: 50,
|
|
ambient: 150
|
|
);
|
|
}
|
|
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: Activa o desactiva el modo debug para mostrar triángulos individuales de curvas
|
|
/// </summary>
|
|
/// <param name="enableDebug">True para activar debug, false para modo normal</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] Debug triangles mode: {(enableDebug ? "ENABLED" : "DISABLED")}");
|
|
|
|
if (wasChanged && forceRefresh)
|
|
{
|
|
RefreshCurveVisualizations();
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
/// ✅ NUEVO: Obtiene información detallada de debug sobre los triángulos de una curva
|
|
/// Útil para debug sin cambiar la visualización
|
|
/// </summary>
|
|
/// <param name="curve">Curva para analizar</param>
|
|
/// <returns>Información de debug como string</returns>
|
|
public string GetCurveDebugInfo(simCurve curve)
|
|
{
|
|
if (curve == null)
|
|
return "ERROR: Curva es null";
|
|
|
|
try
|
|
{
|
|
var triangles = curve.GetOriginalTriangles();
|
|
var debugInfo = new System.Text.StringBuilder();
|
|
|
|
debugInfo.AppendLine($"=== DEBUG INFO PARA CURVA ===");
|
|
debugInfo.AppendLine($"Parámetros de curva:");
|
|
debugInfo.AppendLine($" - Radio Interior: {curve.InnerRadius}");
|
|
debugInfo.AppendLine($" - Radio Exterior: {curve.OuterRadius}");
|
|
debugInfo.AppendLine($" - Ángulo Inicio: {curve.StartAngle} rad ({curve.StartAngle * 180 / Math.PI:F1}°)");
|
|
debugInfo.AppendLine($" - Ángulo Fin: {curve.EndAngle} rad ({curve.EndAngle * 180 / Math.PI:F1}°)");
|
|
debugInfo.AppendLine($"");
|
|
debugInfo.AppendLine($"Triángulos en BEPU: {triangles.Count}");
|
|
|
|
for (int i = 0; i < Math.Min(triangles.Count, 10); i++) // Mostrar máximo 10 triángulos
|
|
{
|
|
var triangle = triangles[i];
|
|
debugInfo.AppendLine($" Triángulo {i + 1}:");
|
|
debugInfo.AppendLine($" P1: ({triangle[0].X:F3}, {triangle[0].Y:F3}, {triangle[0].Z:F3})");
|
|
debugInfo.AppendLine($" P2: ({triangle[1].X:F3}, {triangle[1].Y:F3}, {triangle[1].Z:F3})");
|
|
debugInfo.AppendLine($" P3: ({triangle[2].X:F3}, {triangle[2].Y:F3}, {triangle[2].Z:F3})");
|
|
}
|
|
|
|
if (triangles.Count > 10)
|
|
{
|
|
debugInfo.AppendLine($" ... y {triangles.Count - 10} triángulos más");
|
|
}
|
|
|
|
debugInfo.AppendLine($"");
|
|
debugInfo.AppendLine($"Modo debug actual: {(DebugShowIndividualTriangles ? "ACTIVADO" : "DESACTIVADO")}");
|
|
|
|
return debugInfo.ToString();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"ERROR obteniendo info de debug: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
/// <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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum CameraView
|
|
{
|
|
Top,
|
|
Side,
|
|
Front,
|
|
Isometric
|
|
}
|
|
} |