CtrEditor/Simulacion/BEPUVisualization3D.cs

1090 lines
47 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
/// </summary>
public class BEPUVisualization3DManager
{
private HelixViewport3D viewport3D;
private SimulationManagerBEPU simulationManager;
private Dictionary<simBase, ModelVisual3D> simBaseToModelMap;
private Dictionary<simBase, ShapeDimensions> lastKnownDimensions;
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)
{
System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CYLINDER visualization for simBotella (BEPU uses sphere)");
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)
{
System.Diagnostics.Debug.WriteLine($"[3D Create] Creating CURVE visualization for simCurve");
visual3D = CreateCurveVisualization(curve);
}
else
{
// Para otros objetos, usar la forma real de BEPU
var dimensions = GetShapeDimensions(shapeIndex, simObj);
System.Diagnostics.Debug.WriteLine($"[3D Create] Shape type: {dimensions.ShapeType}, Radius: {dimensions.Radius}");
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;
System.Diagnostics.Debug.WriteLine($"[3D Create] Successfully created 3D visualization for {simObj.GetType().Name}");
}
}
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);
Material material;
if (simObj is simDescarte)
{
// Material semi-transparente para descartes (85% transparente = 15% opacidad)
var descarteBrush = new SolidColorBrush(Color.FromRgb(255, 100, 255)); // Magenta
descarteBrush.Opacity = 0.15; // 15% opacidad = 85% transparencia
material = MaterialHelper.CreateMaterial(
descarteBrush,
specularPower: 60,
ambient: 120
);
}
else
{
// Material plástico brillante para botellas (rojo)
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(255, 80, 80)),
specularPower: 120,
ambient: 200
);
}
var model = new GeometryModel3D(meshBuilder.ToMesh(), material);
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);
// Determinar el color según el tipo de simBase y crear material plástico brillante
Material material;
if (simObj is simGuia)
{
// Material plástico brillante negro para guías
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(50, 50, 50)),
specularPower: 150,
ambient: 180
);
}
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
material = MaterialHelper.CreateMaterial(
yellowBrush,
specularPower: 50,
ambient: 150
);
}
else if (simObj is simTransporte)
{
// Material plástico brillante verde para transportes
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
specularPower: 140,
ambient: 200
);
}
else
{
// Material plástico brillante gris por defecto
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(128, 128, 128)),
specularPower: 100,
ambient: 190
);
}
var model = new GeometryModel3D(meshBuilder.ToMesh(), material);
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);
// Determinar el color según el tipo de simBase - Material plástico con reflexión (valores originales)
Material material;
if (simObj is simBotella)
{
// Material plástico brillante rojo para botellas (cilindros)
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Colors.Red),
specularPower: 120,
ambient: 200
);
}
else if (simObj is simTransporte)
{
// Material plástico verde para transportes (valores originales)
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
specularPower: 140,
ambient: 200
);
}
else if (simObj is simGuia)
{
// Material plástico gris oscuro para guías (valores originales)
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(50, 50, 50)),
specularPower: 150,
ambient: 180
);
}
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
material = MaterialHelper.CreateMaterial(
yellowBrush,
specularPower: 50,
ambient: 150
);
}
else
{
// Material plástico gris estándar para objetos no identificados (valores originales)
material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(128, 128, 128)),
specularPower: 100,
ambient: 190
);
}
var geometry = meshBuilder.ToMesh();
var model = new GeometryModel3D(geometry, material);
var visual = new ModelVisual3D();
visual.Content = model;
return visual;
}
private ModelVisual3D CreateCurveVisualization(simCurve curve)
{
try
{
var meshBuilder = new MeshBuilder();
// ✅ NUEVO: Usar directamente los triángulos originales de BEPU (debug real)
CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
// Material plástico brillante para curvas (azul verdoso)
var material = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 150, 200)),
specularPower: 130,
ambient: 190
);
var geometry = meshBuilder.ToMesh();
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();
System.Diagnostics.Debug.WriteLine($"[3D Debug] Creating curve mesh from {originalTriangles.Count} BEPU triangles");
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
}
System.Diagnostics.Debug.WriteLine($"[3D Debug] Successfully created mesh from BEPU triangles");
}
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>
/// 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);
// ✅ LOGGING: Debug para curvas
if (simObj is simCurve && dimensionsChanged)
{
System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! Old: Inner={lastDimensions.InnerRadius}, Outer={lastDimensions.OuterRadius}, Start={lastDimensions.StartAngle}, End={lastDimensions.EndAngle}");
System.Diagnostics.Debug.WriteLine($"[3D Update] CURVE dimensions changed! New: Inner={currentDimensions.InnerRadius}, Outer={currentDimensions.OuterRadius}, Start={currentDimensions.StartAngle}, End={currentDimensions.EndAngle}");
}
}
else
{
dimensionsChanged = true; // Primera vez
System.Diagnostics.Debug.WriteLine($"[3D Update] First time creating geometry for {simObj.GetType().Name}");
}
// Si las dimensiones cambiaron, recrear la geometría
if (dimensionsChanged)
{
System.Diagnostics.Debug.WriteLine($"[3D Update] Recreating geometry for {simObj.GetType().Name}");
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
System.Diagnostics.Debug.WriteLine($"[3D GetDimensions] Curve - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}");
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();
// Material específico para botellas - rojo brillante
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Colors.Red),
specularPower: 120,
ambient: 200
);
}
// Caso especial: simCurve necesita recreación completa de geometría
else if (simObj is simCurve curve)
{
System.Diagnostics.Debug.WriteLine($"[3D Recreate] Creating CURVE mesh from BEPU triangles - Inner: {curve.InnerRadius}, Outer: {curve.OuterRadius}, Start: {curve.StartAngle}, End: {curve.EndAngle}");
var meshBuilder = new MeshBuilder();
// ✅ NUEVO: Usar directamente los triángulos de BEPU (debug real)
CreateCurveMeshFromBEPUTriangles(meshBuilder, curve);
newGeometry = meshBuilder.ToMesh();
// Material específico para curvas - azul verdoso brillante
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 150, 200)),
specularPower: 130,
ambient: 190
);
System.Diagnostics.Debug.WriteLine($"[3D Recreate] CURVE mesh created successfully with {newGeometry?.Positions?.Count ?? 0} vertices");
}
else if (dimensions.ShapeType == BepuPhysics.Collidables.Sphere.Id)
{
var meshBuilder = new MeshBuilder();
meshBuilder.AddSphere(new Point3D(0, 0, 0), dimensions.Radius);
newGeometry = meshBuilder.ToMesh();
// Color según tipo de objeto - Material plástico con reflexión (valores originales)
if (simObj is simDescarte)
{
// Material semi-transparente para descartes (85% transparente = 15% opacidad)
var descarteBrush = new SolidColorBrush(Color.FromRgb(255, 100, 255)); // Magenta
descarteBrush.Opacity = 0.15; // 15% opacidad = 85% transparencia
newMaterial = MaterialHelper.CreateMaterial(
descarteBrush,
specularPower: 60,
ambient: 120
);
}
else if (simObj is simBotella)
{
// Material plástico brillante rojo para botellas
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Colors.Red),
specularPower: 120,
ambient: 200
);
}
else if (simObj is simTransporte)
{
// Material plástico verde para transportes (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
specularPower: 140,
ambient: 200
);
}
else if (simObj is simGuia)
{
// Material plástico gris oscuro para guías (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(50, 50, 50)),
specularPower: 150,
ambient: 180
);
}
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
newMaterial = MaterialHelper.CreateMaterial(
yellowBrush,
specularPower: 50,
ambient: 150
);
}
else
{
// Material plástico gris estándar para otras esferas (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(128, 128, 128)),
specularPower: 100,
ambient: 190
);
}
}
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();
// Color según tipo de objeto - Material plástico con reflexión (valores originales)
if (simObj is simTransporte)
{
// Material plástico verde para transportes (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(80, 180, 80)),
specularPower: 140,
ambient: 200
);
}
else if (simObj is simGuia)
{
// Material plástico gris oscuro para guías (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(50, 50, 50)),
specularPower: 150,
ambient: 180
);
}
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
newMaterial = MaterialHelper.CreateMaterial(
yellowBrush,
specularPower: 50,
ambient: 150
);
}
else
{
// Material plástico gris estándar para otros boxes (valores originales)
newMaterial = MaterialHelper.CreateMaterial(
new SolidColorBrush(Color.FromRgb(128, 128, 128)),
specularPower: 100,
ambient: 190
);
}
}
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 = MaterialHelper.CreateMaterial(
new SolidColorBrush(Colors.Green),
specularPower: 135,
ambient: 205
);
}
if (newGeometry != null)
{
geometryModel.Geometry = newGeometry;
geometryModel.Material = newMaterial;
System.Diagnostics.Debug.WriteLine($"[3D Recreate] Successfully recreated geometry for {simObj.GetType().Name}");
}
}
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();
}
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>
/// 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
};
System.Diagnostics.Debug.WriteLine($"[3D Camera] Loaded camera state from persistent storage");
}
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();
System.Diagnostics.Debug.WriteLine($"[3D Camera] Saved camera state to persistent storage");
}
}
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();
System.Diagnostics.Debug.WriteLine($"[3D Camera] Subscribed to camera change events");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[3D Camera] ERROR subscribing to camera events: {ex.Message}");
}
}
}
public enum CameraView
{
Top,
Side,
Front,
Isometric
}
}