partial void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor) { if (renderUIElement.Page?.RootElement == null) { return; } var inverseZViewProj = worldViewProj; inverseZViewProj.Row3 = -inverseZViewProj.Row3; elementUnderMouseCursor = UpdateMouseOver(ref viewport, ref inverseZViewProj, renderUIElement); UpdateTouchEvents(ref viewport, ref inverseZViewProj, renderUIElement, drawTime); }
public void Update(RenderUIElement renderObject, Vector3 virtualResolution) { var nearPlane = virtualResolution.Z / 2; var farPlane = nearPlane + virtualResolution.Z; var zOffset = nearPlane + virtualResolution.Z / 2; var aspectRatio = virtualResolution.X / virtualResolution.Y; var verticalFov = (float)Math.Atan2(virtualResolution.Y / 2, zOffset) * 2; var cameraComponent = new CameraComponent(nearPlane, farPlane) { UseCustomAspectRatio = true, AspectRatio = aspectRatio, VerticalFieldOfView = MathUtil.RadiansToDegrees(verticalFov), ViewMatrix = Matrix.LookAtRH(new Vector3(0, 0, zOffset), Vector3.Zero, Vector3.UnitY), ProjectionMatrix = Matrix.PerspectiveFovRH(verticalFov, aspectRatio, nearPlane, farPlane), }; Update(renderObject, cameraComponent); }
partial void PickingUpdate(RenderUIElement renderUIElement, Viewport viewport, ref Matrix worldViewProj, GameTime drawTime, ref UIElement elementUnderMouseCursor);
public void Update(RenderUIElement renderObject, CameraComponent camera) { var frustumHeight = 2 * (float)Math.Tan(MathUtil.DegreesToRadians(camera.VerticalFieldOfView) / 2); var worldMatrix = renderObject.WorldMatrix; if (renderObject.IsFullScreen) { // If fullscreen UI, apply no rotation worldMatrix = Matrix.Identity; } else { // If billboard, rotate the UI element perpendicular to the camera view vector Matrix.Invert(ref camera.ViewMatrix, out Matrix viewInverse); if (renderObject.IsBillboard) { var viewInverseRow1 = viewInverse.Row1; var viewInverseRow2 = viewInverse.Row2; // Remove scale of the camera viewInverseRow1 /= viewInverseRow1.XYZ().Length(); viewInverseRow2 /= viewInverseRow2.XYZ().Length(); // Set the scale of the object viewInverseRow1 *= worldMatrix.Row1.XYZ().Length(); viewInverseRow2 *= worldMatrix.Row2.XYZ().Length(); // Set the adjusted world matrix worldMatrix.Row1 = viewInverseRow1; worldMatrix.Row2 = viewInverseRow2; worldMatrix.Row3 = viewInverse.Row3; } if (renderObject.IsFixedSize) { var viewInverseForward = viewInverse.Forward; viewInverseForward.Normalize(); var distanceVec = (worldMatrix.TranslationVector - viewInverse.TranslationVector); Vector3.Dot(ref viewInverseForward, ref distanceVec, out float distance); distance = Math.Abs(distance); var worldScale = frustumHeight * distance * UIComponent.FixedSizeVerticalUnit; // FrustumHeight already is 2 * Tan(FOV / 2) worldMatrix.Row1 *= worldScale; worldMatrix.Row2 *= worldScale; worldMatrix.Row3 *= worldScale; } // If the UI component is not drawn fullscreen it should be drawn as a quad with world sizes corresponding to its actual size worldMatrix = Matrix.Scaling(renderObject.Size / renderObject.Resolution) * worldMatrix; } // Rotation of Pi along 0x to go from UI space to world space worldMatrix.Row2 = -worldMatrix.Row2; worldMatrix.Row3 = -worldMatrix.Row3; Matrix.Multiply(ref worldMatrix, ref camera.ViewMatrix, out Matrix worldViewMatrix); Matrix.Multiply(ref worldViewMatrix, ref camera.ProjectionMatrix, out WorldViewProjectionMatrix); }
public UIElementState(RenderUIElement renderObject) { RenderObject = renderObject; WorldViewProjectionMatrix = Matrix.Identity; }
private UIElement UpdateMouseOver(ref Viewport viewport, ref Matrix worldViewProj, RenderUIElement state) { if (input == null || !input.HasMouse) { return(null); } var intersectionPoint = Vector3.Zero; var mousePosition = input.MousePosition; var rootElement = state.Page.RootElement; var lastMouseOverElement = state.LastMouseOverElement; UIElement mouseOverElement = lastMouseOverElement; // determine currently overred element. if (mousePosition != state.LastMousePosition || (lastMouseOverElement?.RequiresMouseOverUpdate ?? false)) { Ray uiRay; if (!GetTouchPosition(state.Resolution, ref viewport, ref worldViewProj, mousePosition, out uiRay)) { return(null); } mouseOverElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); } // find the common parent between current and last overred elements var commonElement = FindCommonParent(mouseOverElement, lastMouseOverElement); // disable mouse over state to previously overred hierarchy var parent = lastMouseOverElement; while (parent != commonElement && parent != null) { parent.RequiresMouseOverUpdate = false; parent.MouseOverState = MouseOverState.MouseOverNone; parent = parent.VisualParent; } // enable mouse over state to currently overred hierarchy if (mouseOverElement != null) { // the element itself mouseOverElement.MouseOverState = MouseOverState.MouseOverElement; // its hierarchy parent = mouseOverElement.VisualParent; while (parent != null) { if (parent.IsHierarchyEnabled) { parent.MouseOverState = MouseOverState.MouseOverChild; } parent = parent.VisualParent; } } UIElementUnderMouseCursor = mouseOverElement; // update cached values state.LastMouseOverElement = mouseOverElement; state.LastMousePosition = mousePosition; return(mouseOverElement); }
private void UpdateTouchEvents(ref Viewport viewport, ref Matrix worldViewProj, RenderUIElement state, GameTime gameTime) { var rootElement = state.Page.RootElement; var intersectionPoint = Vector3.Zero; var lastTouchPosition = new Vector2(float.NegativeInfinity); // analyze pointer event input and trigger UI touch events depending on hit Tests foreach (var pointerEvent in compactedPointerEvents) { // performance optimization: skip all the events that started outside of the UI var lastTouchedElement = state.LastTouchedElement; if (lastTouchedElement == null && pointerEvent.EventType != PointerEventType.Pressed) { continue; } var time = gameTime.Total; var currentTouchPosition = pointerEvent.Position; var currentTouchedElement = lastTouchedElement; // re-calculate the element under cursor if click position changed. if (lastTouchPosition != currentTouchPosition) { Ray uiRay; if (!GetTouchPosition(state.Resolution, ref viewport, ref worldViewProj, currentTouchPosition, out uiRay)) { continue; } currentTouchedElement = GetElementAtScreenPosition(rootElement, ref uiRay, ref worldViewProj, ref intersectionPoint); } if (pointerEvent.EventType == PointerEventType.Pressed || pointerEvent.EventType == PointerEventType.Released) { state.LastIntersectionPoint = intersectionPoint; } // TODO: add the pointer type to the event args? var touchEvent = new TouchEventArgs { Action = TouchAction.Down, Timestamp = time, ScreenPosition = currentTouchPosition, ScreenTranslation = pointerEvent.DeltaPosition, WorldPosition = intersectionPoint, WorldTranslation = intersectionPoint - state.LastIntersectionPoint }; switch (pointerEvent.EventType) { case PointerEventType.Pressed: touchEvent.Action = TouchAction.Down; currentTouchedElement?.RaiseTouchDownEvent(touchEvent); break; case PointerEventType.Released: touchEvent.Action = TouchAction.Up; // generate enter/leave events if we passed from an element to another without move events if (currentTouchedElement != lastTouchedElement) { ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); } // trigger the up event currentTouchedElement?.RaiseTouchUpEvent(touchEvent); break; case PointerEventType.Moved: touchEvent.Action = TouchAction.Move; // first notify the move event (even if the touched element changed in between it is still coherent in one of its parents) currentTouchedElement?.RaiseTouchMoveEvent(touchEvent); // then generate enter/leave events if we passed from an element to another if (currentTouchedElement != lastTouchedElement) { ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); } break; case PointerEventType.Canceled: touchEvent.Action = TouchAction.Move; // generate enter/leave events if we passed from an element to another without move events if (currentTouchedElement != lastTouchedElement) { ThrowEnterAndLeaveTouchEvents(currentTouchedElement, lastTouchedElement, touchEvent); } // then raise leave event to all the hierarchy of the previously selected element. var element = currentTouchedElement; while (element != null) { if (element.IsTouched) { element.RaiseTouchLeaveEvent(touchEvent); } element = element.VisualParent; } break; default: throw new ArgumentOutOfRangeException(); } lastTouchPosition = currentTouchPosition; state.LastTouchedElement = currentTouchedElement; state.LastIntersectionPoint = intersectionPoint; } }