@inject IJSRuntime JS @using Excubo.Blazor.Canvas.Contexts
@code { private ElementReference CanvasRef; private ElementReference ViewContainerRef; private double ZoomScale = 1.0; private const double MIN_ZOOM = 0.1; private const double MAX_ZOOM = 5.0; private const double BASE_PIXELS_PER_METER = 50.0; private bool IsMouseInCanvas = false; private double MouseX; private double MouseY; private double OriginX = 0; private double OriginY = 0; private double WorldMouseX; private double WorldMouseY; private double CanvasWidth; private double CanvasHeight; private double CanvasTranslateX = 0; private double CanvasTranslateY = 0; private const double RulerHeight = 20; private const double RobotWidth = 0.606; private const double RobotLength = 1.106; private bool RobotImageLoaded = false; private bool MapImageLoaded = false; private const double ImageX = -10; private const double ImageY = -5; private const double ImageResolution = 0.05; private double MapImageWidth = 0; private double MapImageHeight = 0; private const string MAP_CACHE_KEY = "map_image"; private TouchPoint? LastTouchPoint; private TouchPoint? LastSecondTouchPoint; private double LastTouchDistance = 0; private bool IsTouching = false; protected override async Task OnAfterRenderAsync(bool first_render) { await base.OnAfterRenderAsync(first_render); if (!first_render) return; var containerSize = await JS.InvokeAsync("getElementSize", ViewContainerRef); CanvasWidth = containerSize.Width; CanvasHeight = containerSize.Height; await JS.InvokeVoidAsync("setCanvasSize", CanvasRef, CanvasWidth, CanvasHeight); CanvasTranslateX = CanvasWidth / 2; CanvasTranslateY = CanvasHeight / 2; await LoadRobotImage(); await LoadMapImage(); await DrawCanvas(); } private async Task LoadRobotImage() { try { await JS.InvokeVoidAsync("preloadImage", "images/AMR-250.png"); RobotImageLoaded = true; } catch { RobotImageLoaded = false; } } private async Task LoadMapImage() { try { MapImageLoaded = false; string apiUrl = "api/images/mapping"; await JS.InvokeVoidAsync("preloadImageFromUrl", apiUrl, MAP_CACHE_KEY); var imageDimensions = await JS.InvokeAsync("getImageDimensions", MAP_CACHE_KEY); MapImageWidth = imageDimensions.Width * ImageResolution; MapImageHeight = imageDimensions.Height * ImageResolution; if (MapImageWidth > 0 && MapImageHeight > 0) MapImageLoaded = true; } catch { MapImageLoaded = false; } } private async Task ResetView() { CanvasTranslateX = CanvasWidth / 2; CanvasTranslateY = CanvasHeight / 2; ZoomScale = 1.0; StateHasChanged(); await DrawCanvas(); } private async Task ZoomIn() { const double zoomFactor = 0.15; double oldZoom = ZoomScale; ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor)); if (Math.Abs(ZoomScale - oldZoom) < 0.001) return; await ZoomAtCenter(oldZoom); } private async Task ZoomOut() { const double zoomFactor = 0.15; double oldZoom = ZoomScale; ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor)); if (Math.Abs(ZoomScale - oldZoom) < 0.001) return; await ZoomAtCenter(oldZoom); } private async Task ZoomAtCenter(double oldZoom) { double centerX = CanvasWidth / 2; double centerY = CanvasHeight / 2; double centerWorldX = (centerX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX; double centerWorldY = (centerY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY; double newCenterCanvasX = (centerWorldX + OriginX) * BASE_PIXELS_PER_METER * ZoomScale; double newCenterCanvasY = (centerWorldY + OriginY) * BASE_PIXELS_PER_METER * ZoomScale; CanvasTranslateX = centerX - newCenterCanvasX; CanvasTranslateY = centerY - newCenterCanvasY; if (IsMouseInCanvas) { WorldMouseX = CanvasToWorldX(MouseX); WorldMouseY = CanvasToWorldY(MouseY); } StateHasChanged(); await DrawCanvas(); } private async Task HandleMouseMove(MouseEventArgs e) { MouseX = e.OffsetX; MouseY = e.OffsetY; IsMouseInCanvas = true; WorldMouseX = CanvasToWorldX(MouseX); WorldMouseY = CanvasToWorldY(MouseY); StateHasChanged(); if (e.Buttons == 4) { CanvasTranslateX += e.MovementX; CanvasTranslateY -= e.MovementY; } await DrawCanvas(); } private async Task HandleMouseLeave(MouseEventArgs e) { IsMouseInCanvas = false; MouseX = 0; MouseY = 0; StateHasChanged(); await DrawCanvas(); } private async Task HandleWheel(WheelEventArgs e) { if (e.Buttons == 4) return; const double zoomFactor = 0.1; double oldZoom = ZoomScale; if (e.DeltaY < 0) ZoomScale = Math.Min(MAX_ZOOM, ZoomScale * (1 + zoomFactor)); else ZoomScale = Math.Max(MIN_ZOOM, ZoomScale * (1 - zoomFactor)); if (Math.Abs(ZoomScale - oldZoom) < 0.001) return; MouseX = e.OffsetX; MouseY = e.OffsetY; double zoomPointWorldX = (MouseX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX; double zoomPointWorldY = (MouseY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY; double newZoomPointCanvasX = (zoomPointWorldX + OriginX) * BASE_PIXELS_PER_METER * ZoomScale; double newZoomPointCanvasY = (zoomPointWorldY + OriginY) * BASE_PIXELS_PER_METER * ZoomScale; CanvasTranslateX = MouseX - newZoomPointCanvasX; CanvasTranslateY = MouseY - newZoomPointCanvasY; WorldMouseX = CanvasToWorldX(MouseX); WorldMouseY = CanvasToWorldY(MouseY); StateHasChanged(); await DrawCanvas(); } private async Task HandleTouchMove(TouchEventArgs e) { if (IsTouching) { if (e.Touches.Length == 1) { await HandleSingleTouchMove(e.Touches[0]); } else if (e.Touches.Length == 2) { await HandlePinchZoom(e.Touches[0], e.Touches[1]); } StateHasChanged(); await DrawCanvas(); } } private void HandleTouchStart(TouchEventArgs e) { IsTouching = true; if (e.Touches.Length == 1) { LastTouchPoint = new TouchPoint { X = e.Touches[0].ClientX, Y = e.Touches[0].ClientY }; } else if (e.Touches.Length == 2) { LastTouchPoint = new TouchPoint { X = e.Touches[0].ClientX, Y = e.Touches[0].ClientY }; LastSecondTouchPoint = new TouchPoint { X = e.Touches[1].ClientX, Y = e.Touches[1].ClientY }; LastTouchDistance = CalculateTouchDistance(LastTouchPoint, LastSecondTouchPoint); } } private void HandleTouchEnd(TouchEventArgs e) { IsTouching = false; LastTouchPoint = null; LastSecondTouchPoint = null; LastTouchDistance = 0; } private async Task HandleSingleTouchMove(Microsoft.AspNetCore.Components.Web.TouchPoint touch) { if (LastTouchPoint == null) return; var currentPoint = new TouchPoint { X = touch.ClientX, Y = touch.ClientY }; double deltaX = currentPoint.X - LastTouchPoint.X; double deltaY = currentPoint.Y - LastTouchPoint.Y; CanvasTranslateX += deltaX; CanvasTranslateY += deltaY; LastTouchPoint = currentPoint; var canvasRect = await JS.InvokeAsync("getElementBoundingRect", CanvasRef); MouseX = currentPoint.X - canvasRect.X; MouseY = currentPoint.Y - canvasRect.Y; IsMouseInCanvas = true; WorldMouseX = CanvasToWorldX(MouseX); WorldMouseY = CanvasToWorldY(MouseY); } private async Task HandlePinchZoom(Microsoft.AspNetCore.Components.Web.TouchPoint touch1, Microsoft.AspNetCore.Components.Web.TouchPoint touch2) { if (LastTouchPoint == null || LastSecondTouchPoint == null) return; var currentTouch1 = new TouchPoint { X = touch1.ClientX, Y = touch1.ClientY }; var currentTouch2 = new TouchPoint { X = touch2.ClientX, Y = touch2.ClientY }; double currentDistance = CalculateTouchDistance(currentTouch1, currentTouch2); if (LastTouchDistance > 0) { double distanceRatio = currentDistance / LastTouchDistance; double oldZoom = ZoomScale; ZoomScale = Math.Max(MIN_ZOOM, Math.Min(MAX_ZOOM, ZoomScale * distanceRatio)); if (Math.Abs(ZoomScale - oldZoom) > 0.001) { double centerX = (currentTouch1.X + currentTouch2.X) / 2; double centerY = (currentTouch1.Y + currentTouch2.Y) / 2; var canvasRect = await JS.InvokeAsync("getElementBoundingRect", CanvasRef); double canvasCenterX = centerX - canvasRect.X; double canvasCenterY = centerY - canvasRect.Y; ZoomAtPoint(oldZoom, canvasCenterX, canvasCenterY); } } LastTouchDistance = currentDistance; LastTouchPoint = currentTouch1; LastSecondTouchPoint = currentTouch2; } private double CalculateTouchDistance(TouchPoint point1, TouchPoint point2) { double deltaX = point2.X - point1.X; double deltaY = point2.Y - point1.Y; return Math.Sqrt(deltaX * deltaX + deltaY * deltaY); } private void ZoomAtPoint(double oldZoom, double pointX, double pointY) { double pointWorldX = (pointX - CanvasTranslateX) / oldZoom / BASE_PIXELS_PER_METER - OriginX; double pointWorldY = (pointY - CanvasTranslateY) / oldZoom / BASE_PIXELS_PER_METER - OriginY; double newPointCanvasX = (pointWorldX + OriginX) * BASE_PIXELS_PER_METER * ZoomScale; double newPointCanvasY = (pointWorldY + OriginY) * BASE_PIXELS_PER_METER * ZoomScale; CanvasTranslateX = pointX - newPointCanvasX; CanvasTranslateY = pointY - newPointCanvasY; } private LaserScanData GenerateLaserScanData() { // Robot position (in world coordinates) double robotX = 2; // Robot at origin for demo double robotY = 2; double robotOrientation = 0; // Robot facing right (0 degrees) Random random = new Random(42); // Fixed seed for consistent pattern // Laser scanner parameters const double maxRange = 8.0; // meters const double minRange = 0.5; // meters (fix: was 7.0, should be minimum) const int numPoints = 270; // Number of laser points const double startAngle = -Math.PI / 2 - Math.PI / 4; const double endAngle = Math.PI / 2 + Math.PI / 4; double angleStep = (endAngle - startAngle) / (numPoints - 1); var scanData = new LaserScanData { RobotX = robotX, RobotY = robotY, RobotOrientation = robotOrientation, }; // Generate laser points for (int i = 0; i < numPoints; i++) { double angle = startAngle + i * angleStep; // Random range with some clustering around obstacles double range; if (random.NextDouble() < 0.3) // 30% chance of obstacles { range = random.NextDouble() * 3.0 + 1.0; // 1-4 meters (obstacles) } else if (random.NextDouble() < 0.1) // 10% chance of very close objects { range = random.NextDouble() * 0.8 + 0.2; // 0.2-1.0 meters } else { range = random.NextDouble() * maxRange * 0.7 + maxRange * 0.3; // Far points } // Add some noise to make it realistic range += (random.NextDouble() - 0.5) * 0.1; range = Math.Max(minRange, Math.Min(maxRange, range)); // Calculate point position relative to robot double pointX = robotX + Math.Cos(angle + robotOrientation) * range; double pointY = robotY + Math.Sin(angle + robotOrientation) * range; scanData.Points.Add(new LaserScanPoint { X = pointX, Y = pointY }); } return scanData; } }