private Task ApplyFace(MapDocument document, PerspectiveCamera camera, Face face, IMapObject parent)
        {
            // Right:       use defined action
            // Alt+Right:   apply + align
            // Shift+Right: apply only
            // Ctrl+Right:  nothing...?

            var action = _rightClickAction;

            if (KeyboardState.Alt)
            {
                action = ClickAction.Apply | ClickAction.AlignToSample;
            }
            if (KeyboardState.Shift)
            {
                action = ClickAction.Apply;
            }

            var sampleFace = GetSelection(document).FirstOrDefault();

            var activeTexture = document.Map.Data.GetOne <ActiveTexture>()?.Name ?? sampleFace?.Texture.Name ?? "";

            if (String.IsNullOrWhiteSpace(activeTexture))
            {
                return(Task.CompletedTask);
            }

            var clone = (Face)face.Clone();

            bool changed = false;

            // Apply texture
            if (!String.IsNullOrWhiteSpace(activeTexture) && action.HasFlag(ClickAction.Apply))
            {
                clone.Texture.Name = activeTexture;
                changed            = true;
            }

            // Apply values
            if (camera != null && action.HasFlag(ClickAction.AlignToView))
            {
                // align to camera
                var uaxis = camera.GetRight();
                var vaxis = camera.GetUp();
                var pos   = camera.Location;
                clone.Texture.SetRotation(0);
                clone.Texture.XScale = 1;
                clone.Texture.YScale = 1;
                clone.Texture.UAxis  = uaxis;
                clone.Texture.VAxis  = vaxis;
                clone.Texture.XShift = Vector3.Dot(uaxis, pos);
                clone.Texture.YShift = Vector3.Dot(vaxis, pos);
                changed = true;
            }
            else if (sampleFace != null && action.HasFlag(ClickAction.AlignToSample))
            {
                // align to face
                clone.Texture.AlignWithTexture(clone.Plane, sampleFace.Plane, sampleFace.Texture);
                changed = true;
            }
            else if (sampleFace != null && action.HasFlag(ClickAction.Values))
            {
                // apply values
                clone.Texture.SetRotation(sampleFace.Texture.Rotation);
                clone.Texture.XScale = sampleFace.Texture.XScale;
                clone.Texture.XShift = sampleFace.Texture.XShift;
                clone.Texture.YScale = sampleFace.Texture.YScale;
                clone.Texture.YShift = sampleFace.Texture.YShift;
                changed = true;
            }

            if (!changed)
            {
                return(Task.CompletedTask);
            }

            var edit = new Transaction(
                new RemoveMapObjectData(parent.ID, face),
                new AddMapObjectData(parent.ID, clone)
                );

            return(MapDocumentOperation.Perform(document, edit));
        }
        private Task SelectFace(MapDocument document, PerspectiveCamera camera, Face face, IMapObject parent)
        {
            // Left:       use defined action
            // Alt+Left:   lift
            // Shift+Left: lift + select all
            // Ctrl+Left:  use toggle selection

            var action = _leftClickAction;

            if (KeyboardState.Alt)
            {
                action = ClickAction.Lift;
            }
            if (KeyboardState.Shift || KeyboardState.Ctrl)
            {
                action = ClickAction.Select | ClickAction.Lift;
            }

            var sel = GetSelection(document);

            if (action.HasFlag(ClickAction.Lift))
            {
                // Only sample the face if we're selecting it
                if (!KeyboardState.Ctrl || !sel.IsSelected(parent, face))
                {
                    SetActiveTexture(document, face);
                }
            }

            // Just sample texture without changing selection
            if (!action.HasFlag(ClickAction.Select))
            {
                return(Task.CompletedTask);
            }

            // Clear selection if ctrl isn't down
            if (!KeyboardState.Ctrl)
            {
                sel.Clear();
            }

            // Get the list of faces to toggle selection
            var faces = new HashSet <Face> {
                face
            };

            // If shift is down, select all
            if (KeyboardState.Shift)
            {
                faces.UnionWith(parent.Data.OfType <Face>());
            }

            // Toggle selection of all faces
            foreach (var tf in faces)
            {
                if (sel.IsSelected(parent, tf))
                {
                    sel.Remove(parent, tf);
                }
                else
                {
                    sel.Add(parent, tf);
                }
            }

            Oy.Publish("TextureTool:SelectionChanged", GetSelection(document));

            return(Task.CompletedTask);
        }