// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #if USE_LARGE_WORLDS using Real = System.Double; #else using Real = System.Single; #endif using System.Collections.Generic; using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Viewport.Cameras { /// /// Implementation of that simulated the first-person camera which can fly though the scene. /// /// [HideInEditor] public class FPSCamera : ViewportCamera { private Transform _startMove; private Transform _endMove; private float _moveStartTime = -1; /// /// Gets a value indicating whether this viewport is animating movement. /// public bool IsAnimatingMove => _moveStartTime > Mathf.Epsilon; /// /// The target point location. It's used to orbit around it when user clicks Alt+LMB. /// public Vector3 TargetPoint = new Vector3(-200); /// /// Sets view. /// /// The view position. /// The view direction. public void SetView(Vector3 position, Vector3 direction) { if (IsAnimatingMove) return; // Rotate and move Viewport.ViewPosition = position; Viewport.ViewDirection = direction; } /// /// Sets view. /// /// The view position. /// The view rotation. public void SetView(Vector3 position, Quaternion orientation) { if (IsAnimatingMove) return; // Rotate and move Viewport.ViewPosition = position; Viewport.ViewOrientation = orientation; } /// /// Start animating viewport movement to the target transformation. /// /// The target position. /// The target orientation. public void MoveViewport(Vector3 position, Quaternion orientation) { MoveViewport(new Transform(position, orientation)); } /// /// Start animating viewport movement to the target transformation. /// /// The target transform. public void MoveViewport(Transform target) { _startMove = Viewport.ViewTransform; _endMove = target; _moveStartTime = Time.UnscaledGameTime; } /// /// Moves the viewport to visualize the actor. /// /// The actor to preview. public void ShowActor(Actor actor) { Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); ShowSphere(ref sphere); } /// /// Moves the viewport to visualize selected actors. /// /// The actors to show. /// The used orientation. public void ShowActor(Actor actor, ref Quaternion orientation) { Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); ShowSphere(ref sphere, ref orientation); } /// /// Moves the viewport to visualize selected actors. /// /// The actors to show. public void ShowActors(List selection) { if (selection.Count == 0) return; BoundingSphere mergesSphere = BoundingSphere.Empty; for (int i = 0; i < selection.Count; i++) { selection[i].GetEditorSphere(out var sphere); BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); } if (mergesSphere == BoundingSphere.Empty) return; ShowSphere(ref mergesSphere); } /// /// Moves the viewport to visualize selected actors. /// /// The actors to show. /// The used orientation. public void ShowActors(List selection, ref Quaternion orientation) { if (selection.Count == 0) return; BoundingSphere mergesSphere = BoundingSphere.Empty; for (int i = 0; i < selection.Count; i++) { selection[i].GetEditorSphere(out var sphere); BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); } if (mergesSphere == BoundingSphere.Empty) return; ShowSphere(ref mergesSphere, ref orientation); } /// /// Moves the camera to visualize given world area defined by the sphere. /// /// The sphere. public void ShowSphere(ref BoundingSphere sphere) { var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); ShowSphere(ref sphere, ref q); } /// /// Moves the camera to visualize given world area defined by the sphere. /// /// The sphere. /// The camera orientation. public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) { Vector3 position; if (Viewport.UseOrthographicProjection) { position = sphere.Center + Vector3.Backward * orientation * (sphere.Radius * 5.0f); Viewport.OrthographicScale = (float)Vector3.Distance(position, sphere.Center) / 1000; } else { position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); } TargetPoint = sphere.Center; MoveViewport(position, orientation); } /// public override void SetArcBallView(Quaternion orientation, Vector3 orbitCenter, Real orbitRadius) { base.SetArcBallView(orientation, orbitCenter, orbitRadius); TargetPoint = orbitCenter; } /// public override void Update(float deltaTime) { // Update animated movement if (IsAnimatingMove) { // Calculate linear progress float animationDuration = 0.5f; float time = Time.UnscaledGameTime; float progress = (time - _moveStartTime) / animationDuration; // Check for end if (progress >= 1.0f) { // Animation has been finished _moveStartTime = -1; } // Animate camera try { float a = Mathf.Saturate(progress); a = a * a * a; var targetTransform = Transform.Lerp(_startMove, _endMove, a); targetTransform.Scale = Vector3.Zero; Viewport.ViewPosition = targetTransform.Translation; Viewport.ViewOrientation = targetTransform.Orientation; } catch { // Fix camera if lerp failed (eg. large world with NaNs inside) Viewport.ViewPosition = Vector3.Zero; Viewport.ViewOrientation = Quaternion.Identity; } } } /// public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse) { centerMouse = true; if (IsAnimatingMove) return; Viewport.GetInput(out var input); Viewport.GetPrevInput(out var prevInput); var transformGizmo = (Viewport as EditorGizmoViewport)?.Gizmos.Active as TransformGizmoBase; var isUsingGizmo = transformGizmo != null && transformGizmo.ActiveAxis != TransformGizmoBase.Axis.None; // Get current view properties var yaw = Viewport.Yaw; var pitch = Viewport.Pitch; var position = Viewport.ViewPosition; var rotation = Viewport.ViewOrientation; // Compute base vectors for camera movement var forward = Vector3.Forward * rotation; var up = Vector3.Up * rotation; var right = Vector3.Cross(forward, up); // Dolly if (input.IsPanning || input.IsMoving || input.IsRotating) { Vector3.Transform(ref moveDelta, ref rotation, out Vector3 move); position += move; } // Pan if (input.IsPanning) { var panningSpeed = 0.8f; if (Viewport.InvertPanning) { position += up * (mouseDelta.Y * panningSpeed); position += right * (mouseDelta.X * panningSpeed); } else { position -= right * (mouseDelta.X * panningSpeed); position -= up * (mouseDelta.Y * panningSpeed); } } // Move if (input.IsMoving) { // Move camera over XZ plane var projectedForward = Vector3.Normalize(new Vector3(forward.X, 0, forward.Z)); position -= projectedForward * mouseDelta.Y; yaw += mouseDelta.X; } // Rotate or orbit if (input.IsRotating || (input.IsOrbiting && !isUsingGizmo && prevInput.IsOrbiting)) { yaw += mouseDelta.X; pitch += mouseDelta.Y; } // Zoom in/out if (input.IsZooming && !input.IsRotating) { position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f); if (input.IsAltDown) { position += forward * (Viewport.MouseSpeed * 40 * Viewport.MousePositionDelta.ValuesSum); } } // Move camera with the gizmo if (input.IsOrbiting && isUsingGizmo) { centerMouse = false; Viewport.ViewPosition += transformGizmo.LastDelta.Translation; return; } // Update view Viewport.Yaw = yaw; Viewport.Pitch = pitch; if (input.IsOrbiting) { float orbitRadius = Mathf.Max((float)Vector3.Distance(ref position, ref TargetPoint), 0.0001f); Vector3 localPosition = Viewport.ViewDirection * (-1 * orbitRadius); Viewport.ViewPosition = TargetPoint + localPosition; } else { TargetPoint += position - Viewport.ViewPosition; Viewport.ViewPosition = position; } } } }