Can you Box select?

Oct 18, 2010 at 6:50 AM

Is it possible to box select several items?

 

Or is it possible to get the rendered 2d positions of all "things" (geometries?) so we can do some position comparisons on them? (or their 2.5 d positions, with the z ordering?)

Coordinator
Oct 18, 2010 at 11:54 AM

Hi,

could you elaborate a bit on this. I'm not quite following. 

There is a method called Project() on the Viewport class were you get the projected coordinate of a 3D coordinate - you simply give it a 3D point and a world matrix for that point (typically the world of an object).

Also, for selecting there is a method on Viewport called GetNodeAtPosition() were you give it a 2D coordinate and it will find, if any, object is at that position.

Oct 19, 2010 at 2:37 AM

I've just been through this exercise and it was a process that took the better part of two days.  In addition to the math and interacting with the Balder framework, it turns out that the logic we take for granted with icons in an OS (click to select, ctrl-click to multi-select, click-and-drag on background to multi-select, click-and-drag on a selected item to move) is fairly involved.  I don't have time to write anything up tonight, but will be back in the morning.  Thanks!

Oct 20, 2010 at 11:57 AM

What I mean is like what bob said, if you want to select multiple icons in windows, click on the top left, hold mouse button, drag down and right, and it draws a box around the icons.

 

 

I'm (partially) aware of the difficulty involved (I wrote a 3d maths rotation script once... painful process... especially how it was z-x-y - also I had to do it with just integers... luckily we get doubles in c# :P). I was just hoping that the 3d-> 2d translations would exist some where (as it is being rendered so your engine must be doing it) so I thought I would ask

 

The only other way I would do it is if I pull out my old 3d rotational scripts, (Note I have not done this before, but this is the only way I know how to:S so its probably not the best)

from a two points that form my selection box
get two sets of angles (2d -> 3d translation)
get a rotational matrix for each angle,
Then for each geometry rotate it, check that is is in the correct quadrant (for R2 dx > 0&& dy > 0,for R1 dx < 0 && dy < 0, all || on dz > 0)
(d... is the relative displacement from me, to what ever object I'm comparing to)

Is there a better way of doing it?

Coordinator
Oct 20, 2010 at 7:15 PM
Edited Oct 20, 2010 at 7:25 PM

You could probably do it a bit simpler. 

Basically if you get the ray for each of the corners of the 2D square that is representing the selection, build a box from that ray that is the length of the view frustum and then figure out which objects are within that box. Viewport has a method for getting a ray from 2D coordinates, and you can create a bounding box and check for intersection with the boundingspheres on the geometries.

You use the Contains() method on the BoundingBox to figure out if the BoundingSpheres are within it.

 

PS: Not tested - just random thoughts.. :)

Oct 20, 2010 at 7:57 PM

Quick rundown.  (Lots of details to be filled in.)

- On your geometries, handle the MouseLeftButtonDown and MouseLeftButtonUp events.  On the Game, handle those two events plus MouseMove and MouseLeave.  Send all those events (and their args) to a mouse interpreter class.

- The logic in that class should interpret all those events and generate higher-abstraction events:  GeometryClick, BackgroundClick, GeometryDrag, EndGeometryDrag, BackgroundDrag, EndBackgroundDrag

- Then you have to do something in response to those events.  In my case, the geometries (or actually their ViewModels, since I'm using MVVM) listen to GeometryClick and BackgroundClick (and also the state of the Control key on the keyboard) to decide if they are selected or not.  They listen to BackgroundDrag to decide if they are within a selection box (By projecting their position onto screen coordinates and checking if that is within the bounds of the BackgroundDrag box).  There's also a visual element listening to BackgroundDrag to display the selection box properly.

- In my app, you also have the ability to drag geometries around.  That's another huge component.  I listen to GeometryDrag and EndGeometryDrag, project the mouse coordinates onto a plane in the background, and use movements of that "shadow" to know how far to move the geometry.

Good luck!

Oct 25, 2010 at 4:33 PM

Wanted to post some code.  This is the class that can interpret the low-level mouse events and generate higher-level events from them:

 

using System;
using System.Net;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;


namespace Mouse
{
    public enum MouseButton
    {
        Left,
        Right
    }

    public enum MouseContext
    {
        Background,
        Geometry
    }


    /// <summary>
    /// The IMouseHandlerService can be notified of low-level mouse events (ButtonDown, ButtonUp, Move, Leave) and interprets them
    /// into higher-level events. (Click, Drag)
    /// </summary>
    public interface IMouseHandlerService
    {
        void ButtonDown(MouseButton button, MouseContext contextType, object context);
        void ButtonUp(MouseButton button, MouseContext contextType, object context);
        void Move(System.Windows.Point screenPosition);
        void Leave();
    }


    public class MouseHandlerService : IMouseHandlerService
    {
        #region IMouseHandlerService Members

        public void ButtonDown(MouseButton button, MouseContext contextType, object context)
        {
            this.downPoint = this.mousePosition;
            this.contextType = contextType;
            this.button = button;
            this.context = context;
            this.mouseState = MouseStates.Down;
        }

        public void ButtonUp(MouseButton button, MouseContext contextType, object context)
        {
            switch (this.mouseState)
            {
                case MouseStates.Up:
                    break;
                case MouseStates.Down:
                    if (contextType == this.contextType && button == this.button && context == this.context)
                    { click(); }
                    break;
                case MouseStates.Dragging:
                    endDrag();
                    break;
            }
            this.mouseState = MouseStates.Up;
        }

        public void Move(System.Windows.Point screenPosition)
        {
            this.mousePosition = screenPosition;

            switch (this.mouseState)
            {
                case MouseStates.Up:
                    break;
                case MouseStates.Down:
                    drag();
                    this.mouseState = MouseStates.Dragging;
                    break;
                case MouseStates.Dragging:
                    drag();
                    break;
            }
        }

        public void Leave()
        {
            switch (this.mouseState)
            {
                case MouseStates.Up:
                    break;
                case MouseStates.Down:
                    break;
                case MouseStates.Dragging:
                    endDrag();
                    break;
            }
            this.mouseState = MouseStates.Up;
        }

        #endregion

        #region Private Members

        private enum MouseStates
        {
            Up,
            Down,
            Dragging
        }

        private MouseStates mouseState = MouseStates.Up;
        private System.Windows.Point mousePosition;
        private System.Windows.Point downPoint;
        private MouseContext contextType;
        private MouseButton button;
        private object context = null;

        private void click()
        {
            if (this.button == MouseButton.Left)
            {
                switch (this.contextType)
                {
                    case MouseContext.Background:
                        // The background was clicked - fire off an event somewhere
                        break;
                    case MouseContext.Geometry:
                        // A geometry was clicked - fire off an event somewhere.  This.context can identify the geometry.
                        break;
                }
            }
        }

        private void drag()
        {
            if (this.button == MouseButton.Left)
            {
                switch (this.contextType)
                {
                    case MouseContext.Background:
                        // There has been a click-and-drag on the background.  Use this.downPoint and this.mousePosition to know the bounds of the drag.
                        break;
                    case MouseContext.Geometry:
                        // There has been a click-and-drag on a geometry.  Use this.downPoint and this.mousePosition to know the bounds of the drag.  This.context can identify the geometry.
                        break;
                }
            }
        }

        private void endDrag()
        {
            if (this.button == MouseButton.Left)
            {
                switch (this.contextType)
                {
                    case MouseContext.Background:
                        // Background drag ended.  Fire off an event somewhere.
                        break;
                    case MouseContext.Geometry:
                        // Geometry drag ended.  Fire off an event somewhere.
                        break;
                }
            }
        }

        #endregion


    }

}