Event Handling
Overview
The HEventManager class receives and dispatches events from one object to another. By registering objects derived from a specific HEventListener class with the EventManager, it is possible to route one event (which is essentially a function call) to multiple recipients. This is useful in any situation that requires events to be received by more than one object and/or in which the potential recipients of an event are not known in advance.
Creating a new Listener Type
MVO already uses a number of predefined EventListeners for mouse input, animation events and screen updates. However, it is easy to create a new Listener type. Here is an example:
Event Registration
Each object that is interested in receiving events (in this case Update related) needs to subclass from this Event Listener and must implement at least the functions that it is interested in:
The object must also register itself with the EventManager it wants to receive the events from. This will be usually be the EventManager associated with the active model. To register itself with the EventManager, the function RegisterHandler is called with a pointer to the object that needs to be registered, the type of event that this object should be registered for (one object can derive from multiple event listeners) and a constant indicating the priority with which this object receives this event type. A higher priority means that this object will "see" the event before another object with a lower priority.
Creating a New Event
To create an event from anywhere inside the application, the HLISTENER_EVENT macro should be used. The first parameter is the event listener that this event belongs to, the second parameter is a pointer to the Event Manager and the third parameter is the function call itself:
When executed, the code behind this macro will inject an event into the event queue.
HEventInfo
Mouse clicks and key presses are some of the most common user events. In HOOPS/MVO, the HMouseListener class defines a set of interfaces that handles these events. The methods specified in HMouseListener all take one parameter, an HEventInfo object. The HEventInfo class encapsulates information like mouse pointer position and various keyboard states and is platform independent.
When your application receives a mouse or keyboard event from the system, the raw data is likely to be in a platform specific format. In your method for handling the event, you will need to repackage this information into an HEventInfo object before passing it on. For example, to process a mouse event, create an instance of HEventInfo and initialize it using HEventInfo::SetPoint, which requires the following parameters:
- Type of event
- x and y locations in pixel space where the event occurred
- Flags associated with the event
The following sample code shows how you might handle a left mouse button double click event:
MapFlags is a utility function that maps the UI-specific event-state flags to platform-independent HOOPS/MVO flags. It is part of both the HOOPS/MFC and HOOPS/QT integrations; refer to CHoopsView::MapFlags and HQWidget::MapFlags for more details.
Note that all ASCII characters are handled. However, only certain special keys are detected in a platform-independent way. These keys are listed below:
- Ctrl
- Alt
- Shift
- Up arrow
- Down arrow
- Left arrow
- Right arrow
Other keys may be detected, however, the integer value of those key presses is not guaranteed to be equivalent across all platforms. If you require the use of unsupported keys, we recommend that you set your own value for the key when building the event, then test for that value when processing the event in your operator.
3D Mouse/Joystick Events
In addition to traditional events like mouse movement and key presses, HOOPS/MVO also provides a predefined event listener for feedback from 3D devices such as 3D mice and joysticks via the HJoyStickListener class.
To utilize HJoyStickListener's capabilities, a class must inherit from this event listener.
During the initialization of the class, you need to register with the Event Manager to receive notice of the joystick events. The class must also implement HJoyStickListener's two main methods: OnJoyStickRotation, OnJoyStickTranslation and OnJoyStickButton.
The HJoyStickListener::OnJoyStickRotation is called whenever a rotation event occurs with the 3D mouse or joystick. When this method is called, an HEventInfo object is passed as a parameter. You can use the HEventInfo::GetJoystickRotation method to access the current rotation of x, y and z axes.
Note that this information is raw data from the device, we recommend that you invest some time in fine tuning how your application wishes to interpret this data.
When a translation event occurs with the joystick or 3D mouse, the OnJoyStickTranslation method is called and an HEventInfo object is passed. Using the HEventInfo::GetJoystickTranslation method, you can access the current translation vector. As with the rotation event, the information for the translation vector is also raw data from the device. We suggest that you determine how you want to interpret this information such that it represents the best experience for your user.
If a button is pressed, the OnJoyStickButton method is called. In the HEventInfo object, you can determine which 3D mouse button was pressed by calling HEventInfo::GetFlags(). HOOPS supports up to eight mouse buttons from MVO_JOYSTICK_BUTTON_1 to MVO_JOYSTICK_BUTTON_8.
The following sample code shows how a class would access and utilize the events provided by the HJoyStickListener event listener on the Microsoft Windows platform.
Since HOOPS/MVO is platform independent, your application must implement code to capture joystick events from the system and dispatch these events to HOOPS/MVO. For example, if your application runs on Microsoft Windows, a convenient method for managing 3D mouse/joystick events is via the DirectInput API. The sample code below is from the MFC project found in <hoops>/Dev_Tools/hoops_mfc/source/hoops_mfcu_mgk_dinput_[version].vcxproj.
It shows how you can implement an integration between DirectInput and HOOPS/MVO for 3D mouse/joystick events with the following steps:
- Initialize the connection with DirectInput.
- Poll for 3D mouse/joystick information in your application's timer function.
- Get 3D mouse/joystick states from DirectInput.
- Package the 3D mouse/joystick states into an instance of HEventInfo.
- Dispatch the event using the HLISTENER_EVENT macro.
For a more comprehensive look at the implementation, please see the HOOPS/MFC project found in <hoops>/Dev_Tools/hoops_mfc/source/hoops_mfcu_mgk_[version].vcxproj. The DirectInput integration is enabled with the DIRECT_INPUT_SUPPORT flag.
Timer Events
The HTManager object in MVO provides a centralized timing service for MVO and allows the developer to register operators which will receive time-based function callbacks. Using timer-based operators allows developers to create operators which are interruptable and can be forced to occur over a user-determined span of time. Timer-based events are used extensively for animations and playback sequences.
To best demonstrate how to create a timer-based operator, we will review the HTCObjectRotate operator in MVO. This was added to MVO as a reference implementation. It allows you to select an object and get it to rotate about its centroid for 10 seconds and then stop. At any point, selecting on the object again will stop it rotating. Since the operator is timer based you can continue to use other operators to navigate about the scene or select any of the timer objects to stop before the 10-second rotation has completed.
For any timer-based operators you must derive from the HTClient class. Since we are implementing an operator, we will also inherit the HBaseOperator class. Note, timer-based events do not have to be operators, for example the smooth camera transition mechanism in MVO is also based on a timer event.
Let's look at our HTCObjectRotate class.
The first thing you need to do is register your timer client with HTManager, the timer manager object in MVO. This work is done in the HTCObjectRotate::OnLButtonDown. When a LButtonEvent occurs this operator either registers or removes a timer client from HTManager.
HTC_ObjectRotate::SearchAndDestroy simply queries the object to see if a user option is set on it and if it is then it UnRegisters it, effectively stopping the object being animated. Note, we store a pointer to a segment's timer event in the HOOPS database and use the existence of this to determine whether or not the object is currently in a timer event and if so we then use that pointer to unregister it. This is custom to this specific operator and may not be the right approach for your own timer client. You may not need to determine whether or not an object is already in a timer event or you may want to store this information in a separate data structure. We put this information in the HOOPS database for convenience.
Now let's look at HTCObjectRotate::Animate. Before the code below, we moved the selected object into a unique segment and set a user option on it so that HTCObjectRotate::SearchAndDestroyHTCOR can later determine if it is already in a timer event. You should review the style enums section in the Reference Manual for details on the different style types. Note, you are not guaranteed to receive at your requested rate. However the timer manager will, depending on your style setting, let you know if you have been notified at the requested rate.
Now that we have registered our client with the timer manager and have set a periodic style, our implementation of HTClient::Tick will be called with the time at which it should have been called and also the actual time that the function is being called. This information allows us to implement logic which ensures that the operation occurs in the pre-defined time. In this case, we check if the elapsed time is greater than the duration (which is set as 10 seconds in the constructor). If so, then we UnRegister the client. Otherwise, we set a modelling matrix on the segment based on the elapsed time.
Finally let's look at HTCObjectRotate::UnRegister. It simply tells the manager to remove it from the list of objects receiving timer events and then unsets any user values that may have been previously set on the scene graph. It also unregisters it from the list of objects that should be notified if the HBaseView object is destroyed. If the flag is set, it also deletes the object.
Touch Events
HOOPS/MVO provides multi-touch support, which can be utilized under supported HOOPS platforms that offer touch interfaces. (For example, iOS)
HBaseView provides a method called HBaseView::GetTouchManager. This returns the HTouchManager object, which allows for persistent handling of touches across multiple interaction events. HTouchManager::AddTouch() inserts new touches in the HTouchManager list, or updates an existing touch instance. HTouchManager::GetTouches() returns a list of touch instances. Each touch should have a unique identifier which allows HTouchManager to track touches across events.
For example, let's say 4 'touch down' events come in from the OS. The application code would call HTouchManager::AddTouch() four times. Then, consistent wtih the listener concepts covered above, HLISTENER_EVENT would get called with the appropriate touch event. Let's look at the application/GUI-level code from the HOOPS iOS demo, which handles the various touch events and dispatches them using HLISTENER_EVENT.
The following is a utility method called AddTouch which conversts the GUI 'pixel space' coordinate to 'window space' (which is the coorindate system required by all the HOOPS/3DGS selection functions), and then calls HTouchManager::AddTouch to add the touch to the list of touches:
The following is an implementation of a GUI-specific 'touchesBegan' method. Note the various cases:
- if there is a single touch (i.e. a single finger touches the screen) with a double-tap, it will perform a selection
- if there are 3 touches (i.e. three fingers touch the screen) it will do a FitWorld (aka 'zoom to extents').
- otherwise, it will pass all the touch events onto any objects/operators that are 'listening' to the touch events, and the events are dispatched via HBaseOperator::OnTouchesDown:
In addition to the various touch events being properly handled/dispatched in the GUI-specific event handler methods, each touch-enabled MVO HBaseOperator class needs to implement the HBaseOperator::OnTouchesDown, HBaseOperator::OnTouchesMove, and HBaseOperator::OnTouchesUp as require by the operator's specific goals. HOOPS/MVO already includes touch-capable variants of the standard Camera Operators:
Each of these MVO Operator classes is provided in source code form. This gives you a reference of how you would access the touch information coming in via the HEventInfo class, if case you wish to create custom touch-capable operators derived from HBaseOperator.