Merge branch 'input-overhaul' of github.com:grimfang/panda3d into input-overhaul

This commit is contained in:
rdb 2016-11-22 12:15:37 +01:00
commit d3008c56e0
43 changed files with 1669 additions and 90 deletions

View File

@ -173,6 +173,7 @@ class ShowBase(DirectObject.DirectObject):
self.trackball = None
self.texmem = None
self.showVertices = None
self.deviceButtonThrowers = []
## This is a NodePath pointing to the Camera object set up for the 3D scene.
## This is usually a child of self.camera.
@ -297,6 +298,7 @@ class ShowBase(DirectObject.DirectObject):
## The global job manager, as imported from JobManagerGlobal.
self.jobMgr = jobMgr
## Particle manager
self.particleMgr = None
self.particleMgrEnabled = 0
@ -309,6 +311,12 @@ class ShowBase(DirectObject.DirectObject):
## This is the global input device manager, which keeps track of
## connected input devices.
self.devices = InputDeviceManager.getGlobalPtr()
# add existing devices to the data graph
for device in self.devices.devices:
self.connectDevice(device)
# Checks for device connection and disconnection
self.accept('connect-device', self.connectDevice)
self.accept('disconnect-device', self.disconnectDevice)
self.createStats()
@ -1660,6 +1668,77 @@ class ShowBase(DirectObject.DirectObject):
return self.mouseWatcherNode.getModifierButtons().isDown(
KeyboardButton.meta())
def connectDevice(self, device):
"""
This function will get called each time a new device got
connected and will add that new device to the data graph.
Each device class will get a specific prefix for thrown events. Those
are currently as follow
gamepad
flight_stick
steering_wheel
dance_pad
mouse
keyboard
unclassified_device
In addition, the index of that device will appended to the prefix,
so for example if you hit the A button of the first connected gamepad
you will get an event like "gamepad0-action_a" the second gamepad will
then be catchable via "gamepad1-button_event" and so on.
Note, each device class will have a separate 0 based index, this way
you can have a gamepad0 as well as a steering_wheel0 and flight_stick0.
All newly created button throwers will be stored in
the deviceButtonThrowers lsit
"""
idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName()))
prefix = "unclassified_device"
if device.getDeviceClass() == InputDevice.DC_gamepad:
prefix = "gamepad"
elif device.getDeviceClass() == InputDevice.DC_flight_stick:
prefix = "flight_stick"
elif device.getDeviceClass() == InputDevice.DC_steering_wheel:
prefix = "steering_wheel"
elif device.getDeviceClass() == InputDevice.DC_dance_pad:
prefix = "dance_pad"
elif device.getDeviceClass() == InputDevice.DC_mouse:
prefix = "mouse"
elif device.getDeviceClass() == InputDevice.DC_keyboard:
prefix = "keyboard"
currentPrefixes = []
for np in self.dataRoot.findAllMatches("**/{}".format(prefix)):
bt = np.node()
currentPrefixes.append(bt.getPrefix())
id = 0
# Find the next free ID for the newly connected device
while "{}{}-".format(prefix, id) in currentPrefixes:
id+=1
# Setup the button thrower for that device and register it's event prefix
bt = idn.attachNewNode(ButtonThrower(prefix))
assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id))
bt.node().setPrefix("{}{}-".format(prefix, id))
# append the new button thrower to the list of device button throwers
self.deviceButtonThrowers.append(bt)
def disconnectDevice(self, device):
"""
This function will get called each time a new device got
connected. It is then used to clean up the given device from the
data graph.
"""
self.notify.debug("Disconnect device {}".format(device.getName()))
idn = self.dataRoot.find("**/{}".format(device.getName()))
for bt in list(self.deviceButtonThrowers):
if bt.getName() == idn.getName():
self.deviceButtonThrowers.remove(bt)
break
idn.removeNode()
def addAngularIntegrator(self):
if not self.physicsMgrAngular:
physics = importlib.import_module('panda3d.physics')
@ -1852,6 +1931,10 @@ class ShowBase(DirectObject.DirectObject):
# Check if there were newly connected devices.
self.devices.update()
# Poll all connected devices.
for device in self.devices.devices:
device.poll()
# traverse the data graph. This reads all the control
# inputs (from the mouse and keyboard, for instance) and also
# directly acts upon them (for instance, to move the avatar).

View File

@ -36,6 +36,9 @@ NotifyCategoryDef(device, "");
ConfigVariableBool asynchronous_clients
("asynchronous-clients", true);
ConfigVariableInt low_battery_level
("low-battery-level", 15);
ConfigureFn(config_device) {
init_libdevice();
}

View File

@ -17,10 +17,12 @@
#include "pandabase.h"
#include "notifyCategoryProxy.h"
#include "configVariableBool.h"
#include "configVariableInt.h"
NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE);
extern ConfigVariableBool asynchronous_clients;
extern ConfigVariableInt low_battery_level;
extern EXPCL_PANDA_DEVICE void init_libdevice();

View File

@ -227,6 +227,7 @@ init_device() {
}
_name.assign(name);
//cerr << "##### Now initializing device " << name << "\n";
struct input_id id;
if (ioctl(_fd, EVIOCGID, &id) >= 0) {
@ -237,12 +238,106 @@ init_device() {
bool all_values_zero = true;
bool emulate_dpad = true;
bool has_keys = false;
bool has_axes = false;
uint8_t keys[(KEY_CNT + 7) >> 3];
if (test_bit(EV_KEY, evtypes)) {
// Check which buttons are on the device.
uint8_t keys[(KEY_CNT + 7) >> 3];
memset(keys, 0, sizeof(keys));
ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys);
has_keys = true;
if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) {
_flags |= IDF_has_keyboard;
}
}
int num_bits = 0;
uint8_t axes[(ABS_CNT + 7) >> 3];
if (test_bit(EV_ABS, evtypes)) {
// Check which axes are on the device.
memset(axes, 0, sizeof(axes));
num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
has_axes = true;
}
// Try to detect which type of device we have here
int device_scores[DC_COUNT];
memset(device_scores, 0, sizeof(device_scores));
// Test for specific keys
if (test_bit(BTN_GAMEPAD, keys)) {
device_scores[DC_gamepad] += 5;
device_scores[DC_steering_wheel] += 5;
device_scores[DC_flight_stick] += 5;
}
if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) {
device_scores[DC_steering_wheel] += 10;
}
if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) {
device_scores[DC_steering_wheel] += 10;
}
if (test_bit(BTN_JOYSTICK, keys)) {
device_scores[DC_flight_stick] += 10;
}
if (test_bit(BTN_MOUSE, keys)) {
device_scores[DC_mouse] += 20;
}
uint8_t unknown_keys[] = {KEY_POWER};
for (int i = 0; i < 1; i++) {
if (test_bit(unknown_keys[i], keys)) {
if (unknown_keys[i] == KEY_POWER) {
}
device_scores[DC_unknown] += 20;
}
}
if (_flags & IDF_has_keyboard) {
device_scores[DC_keyboard] += 20;
}
// Test for specific name tags
string lowercase_name = _name;
for(int x=0; x<_name.length(); x++) {
lowercase_name[x]=tolower(lowercase_name[x]);
}
if (lowercase_name.find("gamepad") != string::npos) {
device_scores[DC_gamepad] += 10;
}
if (lowercase_name.find("wheel") != string::npos) {
device_scores[DC_steering_wheel] += 10;
}
if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) {
device_scores[DC_mouse] += 10;
}
if (lowercase_name.find("keyboard") != string::npos) {
device_scores[DC_keyboard] += 10;
}
// List of lowercase names that occur in unknown devices
string unknown_names[] = {"video bus", "power button", "sleep button"};
for(int i = 0; i < 3; i++) {
if (lowercase_name.find(unknown_names[i]) != string::npos) {
device_scores[DC_unknown] += 20;
}
}
// Check which device type got the most points
size_t highest_score = 0;
for (size_t i = 0; i < DC_COUNT; i++) {
if (device_scores[i] > highest_score) {
highest_score = device_scores[i];
_device_class = (DeviceClass)i;
}
}
//cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n";
if (_device_class != DC_gamepad) {
emulate_dpad = false;
}
if (has_keys) {
// Also check whether the buttons are currently pressed.
uint8_t states[(KEY_CNT + 7) >> 3];
memset(states, 0, sizeof(states));
@ -252,56 +347,28 @@ init_device() {
for (int i = 0; i < KEY_CNT; ++i) {
if (test_bit(i, keys)) {
set_button_map(bi, map_button(i));
//cerr << "Button " << bi << " is mapped by the driver to " << i << "\n";
if (test_bit(i, states)) {
_buttons[bi]._state = S_down;
_buttons[bi].state = S_down;
all_values_zero = false;
} else {
_buttons[bi]._state = S_up;
_buttons[bi].state = S_up;
}
if (_buttons[bi]._handle == GamepadButton::dpad_left()) {
if (_buttons[bi].handle == GamepadButton::dpad_left()) {
emulate_dpad = false;
}
++bi;
}
}
if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) {
_flags |= IDF_has_keyboard;
}
// Check device type.
if (test_bit(BTN_GAMEPAD, keys)) {
_device_class = DC_gamepad;
} else if (test_bit(BTN_JOYSTICK, keys)) {
_device_class = DC_flight_stick;
} else if (test_bit(BTN_MOUSE, keys)) {
_device_class = DC_mouse;
} else if (test_bit(BTN_WHEEL, keys)) {
_device_class = DC_steering_wheel;
} else if (_flags & IDF_has_keyboard) {
_device_class = DC_keyboard;
}
}
if (_device_class != DC_gamepad) {
emulate_dpad = false;
}
if (test_bit(EV_ABS, evtypes)) {
// Check which axes are on the device.
uint8_t axes[(ABS_CNT + 7) >> 3];
memset(axes, 0, sizeof(axes));
if (has_axes) {
AxisRange range;
range._scale = 1.0;
range._bias = 0.0;
_axis_ranges.resize(ABS_CNT, range);
int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
for (int i = 0; i < num_bits; ++i) {
if (test_bit(i, axes)) {
if (i >= ABS_HAT0X) {
@ -319,9 +386,14 @@ init_device() {
_dpad_up_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down()));
} else if (i == ABS_HAT0X) {
set_control_map(i, C_hat_x);
} else if (i == ABS_HAT0Y) {
set_control_map(i, C_hat_y);
}
} else {
set_control_map(i, axis_map[i]);
//cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n";
}
// Check the initial value and ranges.
@ -346,7 +418,7 @@ init_device() {
_axis_ranges[i]._scale = factor;
_axis_ranges[i]._bias = bias;
_controls[i]._state = fma(absinfo.value, factor, bias);
_controls[i].state = fma(absinfo.value, factor, bias);
if (absinfo.value != 0) {
all_values_zero = false;
@ -693,10 +765,18 @@ map_button(int code) {
case BTN_TR2:
return GamepadButton::rtrigger();
case BTN_1:
return GamepadButton::action_1();
case BTN_2:
return GamepadButton::action_2();
case BTN_SELECT:
case KEY_PREVIOUS:
return GamepadButton::back();
case BTN_START:
case KEY_NEXT:
return GamepadButton::start();
case BTN_MODE:

View File

@ -11,6 +11,8 @@
* @date 2015-12-11
*/
#include "config_device.h"
/**
*
*/
@ -197,7 +199,7 @@ set_button_map(int index, ButtonHandle button) {
_buttons.resize(index + 1, ButtonState());
}
_buttons[index]._handle = button;
_buttons[index].handle = button;
}
/**
@ -208,7 +210,7 @@ set_button_map(int index, ButtonHandle button) {
INLINE ButtonHandle InputDevice::
get_button_map(int index) const {
if (index >= 0 && index < (int)_buttons.size()) {
return _buttons[index]._handle;
return _buttons[index].handle;
} else {
return ButtonHandle::none();
}
@ -221,7 +223,7 @@ get_button_map(int index) const {
INLINE bool InputDevice::
get_button_state(int index) const {
if (index >= 0 && index < (int)_buttons.size()) {
return (_buttons[index]._state == S_down);
return (_buttons[index].state == S_down);
} else {
return false;
}
@ -234,12 +236,43 @@ get_button_state(int index) const {
INLINE bool InputDevice::
is_button_known(int index) const {
if (index >= 0 && index < (int)_buttons.size()) {
return _buttons[index]._state != S_unknown;
return _buttons[index].state != S_unknown;
} else {
return false;
}
}
/**
* Returns the ButtonState that is set at the given index, or throw an assection
* if the index was not found in the list.
*/
INLINE InputDevice::ButtonState InputDevice::
get_button(size_t index) const {
if (index >= 0 && index < (int)_buttons.size()) {
return _buttons[index];
} else {
device_cat.error()
<< "Index " << index<< " was not found in the controls list\n";
nassertr(false, ButtonState());
}
}
/**
* Returns the first ButtonState found with the given axis, or throw an assection
* if the button handle was not found in the list.
*/
INLINE InputDevice::ButtonState InputDevice::
find_button(ButtonHandle handle) const {
for (int i; i < (int)_buttons.size(); i++) {
if (_buttons[i].handle == handle) {
return _buttons[i];
}
}
device_cat.error()
<< "Handle " << handle.get_name() << " was not found in the controls list\n";
nassertr(false, ButtonState());
}
/**
* Returns the number of analog controls known to the InputDevice. This number
* may change as more controls are discovered.
@ -257,14 +290,14 @@ get_num_controls() const {
* the various controls by index number.
*/
INLINE void InputDevice::
set_control_map(int index, ControlAxis axis) {
set_control_map(int index, InputDevice::ControlAxis axis) {
LightMutexHolder holder(_lock);
nassertv(index >= 0);
if (index >= (int)_controls.size()) {
_controls.resize(index + 1, AnalogState());
}
_controls[index]._axis = axis;
_controls[index].axis = axis;
}
/**
@ -275,7 +308,7 @@ set_control_map(int index, ControlAxis axis) {
INLINE InputDevice::ControlAxis InputDevice::
get_control_map(int index) const {
if (index >= 0 && index < (int)_controls.size()) {
return _controls[index]._axis;
return _controls[index].axis;
} else {
return C_none;
}
@ -289,12 +322,43 @@ get_control_map(int index) const {
INLINE double InputDevice::
get_control_state(int index) const {
if (index >= 0 && index < (int)_controls.size()) {
return _controls[index]._state;
return _controls[index].state;
} else {
return 0.0;
}
}
/**
* Returns the AnalogAxis that is set at the given index, or throw an assection
* if the index was not found in the list.
*/
INLINE InputDevice::AnalogState InputDevice::
get_control(size_t index) const {
if (index >= 0 && index < (int)_controls.size()) {
return _controls[index];
} else {
device_cat.error()
<< "Index " << index<< " was not found in the controls list\n";
nassertr(false, AnalogState());
}
}
/**
* Returns the first AnalogAxis found with the given axis, or throw an assection
* if the axis was not found in the list.
*/
INLINE InputDevice::AnalogState InputDevice::
find_control(InputDevice::ControlAxis axis) const {
for (int i; i < (int)_controls.size(); i++) {
if (_controls[i].axis == axis) {
return _controls[i];
}
}
device_cat.error()
<< "Axis " << axis << " was not found in the controls list\n";
nassertr(false, AnalogState());
}
/**
* Returns true if the state of the indicated analog control is known, or false
* if we have never heard anything about this particular control.
@ -302,7 +366,7 @@ get_control_state(int index) const {
INLINE bool InputDevice::
is_control_known(int index) const {
if (index >= 0 && index < (int)_controls.size()) {
return _controls[index]._known;
return _controls[index].known;
} else {
return false;
}
@ -377,8 +441,8 @@ operator < (const InputDevice &) const {
*/
INLINE InputDevice::ButtonState::
ButtonState() :
_handle(ButtonHandle::none()),
_state(S_unknown)
handle(ButtonHandle::none()),
state(S_unknown)
{
}
@ -387,8 +451,8 @@ ButtonState() :
*/
INLINE InputDevice::ButtonState::
ButtonState(ButtonHandle handle) :
_handle(handle),
_state(S_unknown)
handle(handle),
state(S_unknown)
{
}
@ -397,8 +461,8 @@ ButtonState(ButtonHandle handle) :
*/
INLINE InputDevice::AnalogState::
AnalogState() :
_axis(C_none),
_state(0.0),
_known(false)
axis(C_none),
state(0.0),
known(false)
{
}

View File

@ -171,12 +171,12 @@ set_button_state(int index, bool down) {
}
State new_state = down ? S_down : S_up;
if (_buttons[index]._state == new_state) {
if (_buttons[index].state == new_state) {
return;
}
_buttons[index]._state = new_state;
_buttons[index].state = new_state;
ButtonHandle handle = _buttons[index]._handle;
ButtonHandle handle = _buttons[index].handle;
if (device_cat.is_spam()) {
device_cat.spam()
@ -208,19 +208,19 @@ set_control_state(int index, double state) {
_controls.resize(index + 1, AnalogState());
}
if (device_cat.is_spam() && _controls[index]._state != state) {
if (device_cat.is_spam() && _controls[index].state != state) {
device_cat.spam()
<< "Changed control " << index;
if (_controls[index]._axis != C_none) {
device_cat.spam(false) << " (" << _controls[index]._axis << ")";
if (_controls[index].known != C_none) {
device_cat.spam(false) << " (" << _controls[index].known << ")";
}
device_cat.spam(false) << " to " << state << "\n";
}
_controls[index]._state = state;
_controls[index]._known = true;
_controls[index].state = state;
_controls[index].known = true;
}
/**
@ -308,13 +308,13 @@ output_buttons(ostream &out) const {
Buttons::const_iterator bi;
for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
const ButtonState &state = (*bi);
if (state._state != S_unknown) {
if (state.state != S_unknown) {
if (any_buttons) {
out << ", ";
}
any_buttons = true;
out << (int)(bi - _buttons.begin()) << "=";
if (state._state == S_up) {
if (state.state == S_up) {
out << "up";
} else {
out << "down";
@ -336,17 +336,17 @@ write_buttons(ostream &out, int indent_level) const {
Buttons::const_iterator bi;
for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) {
const ButtonState &state = (*bi);
if (state._state != S_unknown) {
if (state.state != S_unknown) {
any_buttons = true;
indent(out, indent_level)
<< (int)(bi - _buttons.begin()) << ". ";
if (state._handle != ButtonHandle::none()) {
out << "(" << state._handle << ") ";
if (state.handle != ButtonHandle::none()) {
out << "(" << state.handle << ") ";
}
if (state._state == S_up) {
if (state.state == S_up) {
out << "up";
} else {
out << "down";
@ -372,11 +372,11 @@ write_controls(ostream &out, int indent_level) const {
Controls::const_iterator ai;
for (ai = _controls.begin(); ai != _controls.end(); ++ai) {
const AnalogState &state = (*ai);
if (state._known) {
if (state.known) {
any_controls = true;
indent(out, indent_level)
<< (int)(ai - _controls.begin()) << ". " << state._state << "\n";
<< (int)(ai - _controls.begin()) << ". " << state.state << "\n";
}
}

View File

@ -77,6 +77,9 @@ PUBLISHED:
// Head-mounted display.
DC_hmd,
// Count of this enum, used for loops
DC_COUNT,
};
protected:
@ -247,13 +250,15 @@ public:
S_down
};
PUBLISHED:
class ButtonState {
public:
INLINE ButtonState();
INLINE ButtonState(ButtonHandle handle);
ButtonHandle _handle;
State _state;
PUBLISHED:
ButtonHandle handle;
State state;
};
typedef pvector<ButtonState> Buttons;
Buttons _buttons;
@ -262,9 +267,10 @@ public:
public:
INLINE AnalogState();
ControlAxis _axis;
double _state;
bool _known;
PUBLISHED:
ControlAxis axis;
double state;
bool known;
};
typedef pvector<AnalogState> Controls;
Controls _controls;
@ -274,6 +280,17 @@ public:
TrackerData _tracker_data;
INLINE ButtonState get_button(size_t index) const;
INLINE ButtonState find_button(ButtonHandle handle) const;
INLINE AnalogState get_control(size_t index) const;
INLINE AnalogState find_control(ControlAxis axis) const;
// Make device buttons and controls iterable
MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button);
MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control);
public:
static TypeHandle get_class_type() {
return _type_handle;
@ -297,8 +314,8 @@ INLINE ostream &operator << (ostream &out, const InputDevice &device) {
return out;
}
ostream &operator << (ostream &out, InputDevice::DeviceClass dc);
ostream &operator << (ostream &out, InputDevice::ControlAxis axis);
EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::DeviceClass dc);
EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::ControlAxis axis);
#include "inputDevice.I"

View File

@ -246,23 +246,40 @@ consider_add_js_device(int js_index) {
#endif
/**
* Description: Returns all currently connected gamepad devices.
* Description: Returns all currently connected devices.
*/
InputDeviceSet InputDeviceManager::
get_gamepads() const {
InputDeviceSet gamepads;
get_devices() const {
InputDeviceSet devices;
LightMutexHolder holder(_lock);
for (size_t i = 0; i < _connected_devices.size(); ++i) {
InputDevice *device = _connected_devices[i];
if (device->get_device_class() == InputDevice::DC_gamepad) {
gamepads.add_device(device);
devices.add_device(device);
}
return devices;
}
/**
* Description: Returns all currently connected devices of the given device class.
*/
InputDeviceSet InputDeviceManager::
get_devices(InputDevice::DeviceClass device_class) const {
InputDeviceSet devices;
LightMutexHolder holder(_lock);
for (size_t i = 0; i < _connected_devices.size(); ++i) {
InputDevice *device = _connected_devices[i];
if (device->get_device_class() == device_class) {
devices.add_device(device);
}
}
return gamepads;
return devices;
}
/**
* Called when a new device has been discovered. This may also be used to
* register virtual devices.

View File

@ -37,10 +37,10 @@ private:
InputDevice *consider_add_js_device(int index);
#endif
public:
InputDeviceSet get_gamepads() const;
PUBLISHED:
InputDeviceSet get_devices() const;
InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const;
void add_device(InputDevice *device);
void remove_device(InputDevice *device);
@ -48,8 +48,8 @@ PUBLISHED:
INLINE static InputDeviceManager *get_global_ptr();
// The set of all currently connected gamepad devices.
MAKE_PROPERTY(gamepads, get_gamepads);
// The set of all currently connected devices.
MAKE_PROPERTY(devices, get_devices);
private:
LightMutex _lock;

View File

@ -0,0 +1,71 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file InputDeviceNode.cxx
* @author fireclaw
* @date 2016-07-14
*/
#include "config_device.h"
#include "inputDeviceNode.h"
#include "dataNodeTransmit.h"
#include "inputDeviceManager.h"
TypeHandle InputDeviceNode::_type_handle;
InputDeviceNode::
InputDeviceNode(InputDevice *device, const string &name) :
DataNode(name),
_device(device)
{
_button_events_output = define_output("button_events", ButtonEventList::get_class_type());
_low_battery_event_output = define_output("low_battery_level_event", EventStoreInt::get_class_type());
}
/**
* Redirects the class to get the data from a different device.
*/
void InputDeviceNode::
set_device(InputDevice *device) {
_device = device;
}
/**
* Returns the associated device.
*/
PT(InputDevice) InputDeviceNode::
get_device() const {
return _device;
}
/**
* The virtual implementation of transmit_data(). This function receives an
* array of input parameters and should generate an array of output
* parameters. The input parameters may be accessed with the index numbers
* returned by the define_input() calls that were made earlier (presumably in
* the constructor); likewise, the output parameters should be set with the
* index numbers returned by the define_output() calls.
*/
void InputDeviceNode::
do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
DataNodeTransmit &output) {
// get all button events of the device and forward them to the data graph
if (_device->has_button_event()) {
PT(ButtonEventList) bel = _device->get_button_events();
output.set_data(_button_events_output, EventParameter(bel));
}
// calculate the battery level in percent and set a warning if the level is to low
if (_device->has_battery()) {
short bl = _device->get_battery_level();
short bl_percent = bl / (_device->get_max_battery_level() / (short)100);
if (bl_percent <= low_battery_level) {
output.set_data(_low_battery_event_output, EventParameter(1));
}
}
}

View File

@ -0,0 +1,66 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file InputDeviceNode.h
* @author fireclaw
* @date 2016-07-14
*/
#ifndef INPUTDEVICENODE_H
#define INPUTDEVICENODE_H
#include "pandabase.h"
#include "dataNode.h"
#include "inputDeviceManager.h"
#include "linmath_events.h"
/**
* Reads the controler data sent from the InputDeviceManager, and
* transmits it down the data graph.
*
*
*/
class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode {
PUBLISHED:
InputDeviceNode(InputDevice *device, const string &name);
void set_device(InputDevice *device);
PT(InputDevice) get_device() const;
protected:
// Inherited from DataNode
virtual void do_transmit_data(DataGraphTraverser *trav,
const DataNodeTransmit &input,
DataNodeTransmit &output);
private:
// outputs
int _button_events_output;
int _low_battery_event_output;
PT(InputDevice) _device;
public:
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
DataNode::init_type();
register_type(_type_handle, "InputDeviceNode",
DataNode::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
private:
static TypeHandle _type_handle;
};
#endif // INPUTDEVICENODE_H

View File

@ -163,11 +163,21 @@ open_device() {
handle = GamepadButton::rtrigger();
break;
case BTN_1:
handle = GamepadButton::action_1();
break;
case BTN_2:
handle = GamepadButton::action_2();
break;
case BTN_SELECT:
case KEY_PREVIOUS:
handle = GamepadButton::back();
break;
case BTN_START:
case KEY_NEXT:
handle = GamepadButton::start();
break;
@ -209,7 +219,7 @@ open_device() {
handle = ButtonHandle::none();
break;
}
_buttons[i]._handle = handle;
_buttons[i].handle = handle;
}
}
@ -311,7 +321,7 @@ open_device() {
axis = C_none;
break;
}
_controls[i]._axis = axis;
_controls[i].axis = axis;
}
}
@ -373,7 +383,7 @@ open_device() {
// this gamepad yet (which means it hasn't been plugged in for this session)
if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
for (int i = 0; i < _controls.size(); ++i) {
if (_controls[i]._state != 0.0) {
if (_controls[i].state != 0.0) {
_is_connected = true;
return true;
}
@ -437,7 +447,7 @@ process_events() {
set_button_state(_dpad_up_button+1, events[i].value > 1000);
}
ControlAxis axis = _controls[index]._axis;
ControlAxis axis = _controls[index].axis;
if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) {
// We'd like to use 0.0 to indicate the resting position.

View File

@ -10,3 +10,4 @@
#include "inputDeviceManager.cxx"
#include "inputDeviceSet.cxx"
#include "linuxJoystickDevice.cxx"
#include "inputDeviceNode.cxx"

View File

@ -34,6 +34,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(back)
DEFINE_GAMEPAD_BUTTON_HANDLE(guide)
DEFINE_GAMEPAD_BUTTON_HANDLE(start)
DEFINE_GAMEPAD_BUTTON_HANDLE(next)
DEFINE_GAMEPAD_BUTTON_HANDLE(previous)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_a)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_b)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_c)
@ -41,6 +44,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_x)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_y)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_z)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_1)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_2)
/**
* This is intended to be called only once, by the static initialization
* performed in config_util.cxx.
@ -63,10 +69,16 @@ init_gamepad_buttons() {
ButtonRegistry::ptr()->register_button(_guide, "guide");
ButtonRegistry::ptr()->register_button(_start, "start");
ButtonRegistry::ptr()->register_button(_next, "next");
ButtonRegistry::ptr()->register_button(_previous, "previous");
ButtonRegistry::ptr()->register_button(_action_a, "action_a");
ButtonRegistry::ptr()->register_button(_action_b, "action_b");
ButtonRegistry::ptr()->register_button(_action_c, "action_c");
ButtonRegistry::ptr()->register_button(_action_x, "action_x");
ButtonRegistry::ptr()->register_button(_action_y, "action_y");
ButtonRegistry::ptr()->register_button(_action_z, "action_z");
ButtonRegistry::ptr()->register_button(_action_1, "action_1");
ButtonRegistry::ptr()->register_button(_action_2, "action_2");
}

View File

@ -40,6 +40,9 @@ PUBLISHED:
static ButtonHandle guide();
static ButtonHandle start();
static ButtonHandle next();
static ButtonHandle previous();
static ButtonHandle action_a();
static ButtonHandle action_b();
static ButtonHandle action_c();
@ -47,6 +50,9 @@ PUBLISHED:
static ButtonHandle action_y();
static ButtonHandle action_z();
static ButtonHandle action_1();
static ButtonHandle action_2();
public:
static void init_gamepad_buttons();
};

131
samples/gamepad/gamepad.py Normal file
View File

@ -0,0 +1,131 @@
#!/usr/bin/env python
'''
Demonstrate usage of gamepads and other input devices
In this sample you can use a gamepad type device to control the camera and
show some messages on screen. Using the left stick on the controler will
move the camera where the right stick will rotate the camera.
'''
from direct.showbase.ShowBase import ShowBase
from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3
from direct.gui.OnscreenText import OnscreenText
loadPrcFileData("", "notify-level-device debug")
class App(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# print all events sent through the messenger
self.messenger.toggleVerbose()
self.lblWarning = OnscreenText(
text = "No devices found",
fg=(1,0,0,1),
scale = .25)
self.lblWarning.hide()
self.lblAction = OnscreenText(
text = "Action",
fg=(1,1,1,1),
scale = .15)
self.lblAction.hide()
self.checkDevices()
# Accept device dis-/connection events
# NOTE: catching the events here will overwrite the accept in showbase, hence
# we need to forward the event in the functions we set here!
self.accept("connect-device", self.connect)
self.accept("disconnect-device", self.disconnect)
self.accept("escape", exit)
self.accept("gamepad0-start", exit)
self.accept("flight_stick0-start", exit)
# Accept button events of the first connected gamepad
self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"])
self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"])
self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"])
self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"])
self.environment = loader.loadModel("environment")
self.environment.reparentTo(render)
# disable pandas default mouse-camera controls so we can handle the camera
# movements by ourself
self.disableMouse()
# list of connected gamepad devices
gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
# set the center position of the control sticks
# NOTE: here we assume, that the wheel is centered when the application get started.
# In real world applications, you should notice the user and give him enough time
# to center the wheel until you store the center position of the controler!
self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state
self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state
self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state
self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state
self.taskMgr.add(self.moveTask, "movement update task")
def connect(self, device):
# we need to forward the event to the connectDevice function of showbase
self.connectDevice(device)
# Now we can check for ourself
self.checkDevices()
def disconnect(self, device):
# we need to forward the event to the disconnectDevice function of showbase
self.disconnectDevice(device)
# Now we can check for ourself
self.checkDevices()
def checkDevices(self):
# check if we have gamepad devices connected
if self.devices.get_devices(InputDevice.DC_gamepad):
# we have at least one gamepad device
self.lblWarning.hide()
else:
# no devices connected
self.lblWarning.show()
def doAction(self, showText, text):
if showText and self.lblAction.isHidden():
self.lblAction.show()
else:
self.lblAction.hide()
def moveTask(self, task):
dt = globalClock.getDt()
movementVec = Vec3()
gamepads = base.devices.getDevices(InputDevice.DC_gamepad)
if len(gamepads) == 0:
# savety check
return task.cont
# we will use the first found gamepad
# Move the camera left/right
left_x = gamepads[0].findControl(InputDevice.C_left_x)
movementVec.setX(left_x.state - self.lxcenter)
# Move the camera forward/backward
left_y = gamepads[0].findControl(InputDevice.C_left_y)
movementVec.setY(left_y.state - self.lycenter)
# Control the cameras heading
right_x = gamepads[0].findControl(InputDevice.C_right_x)
base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter))
# Control the cameras pitch
right_y = gamepads[0].findControl(InputDevice.C_right_y)
base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter))
# calculate movement
base.camera.setX(base.camera, 100 * dt * movementVec.getX())
base.camera.setY(base.camera, 100 * dt * movementVec.getY())
return task.cont
app = App()
app.run()

View File

@ -0,0 +1,342 @@
#!/usr/bin/env python
'''
Demonstrate how a simple button mapping gui can be written
'''
from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import (
DGG,
DirectFrame,
DirectButton,
DirectLabel,
OkCancelDialog,
DirectScrolledFrame)
from panda3d.core import (
VBase4,
TextNode,
Vec2,
InputDevice,
loadPrcFileData)
# Make sure the textures look crisp on every device that supports
# non-power-2 textures
loadPrcFileData("", "textures-auto-power-2 #t")
class App(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.setBackgroundColor(0, 0, 0)
# make the font look nice at a big scale
DGG.getDefaultFont().setPixelsPerUnit(100)
# a dict of actions and button/axis events
self.gamepadMapping = {
"Move forward":"Left Stick Y",
"Move backward":"Left Stick Y",
"Move left":"Left Stick X",
"Move right":"Left Stick X",
"Jump":"a",
"Action":"b",
"Sprint":"x",
"Map":"y",
"action-1":"c",
"action-2":"d",
"action-3":"e",
"action-4":"f",
"action-5":"g",
"action-6":"h",
"action-7":"i",
"action-8":"j",
"action-9":"k",
"action-10":"l",
"action-11":"m",
}
# this will store the action that we want to remap
self.actionToMap = ""
# this will store the key/axis that we want to asign to an action
self.newActionKey = ""
# this will store the label that needs to be actualized in the list
self.actualizeLabel = None
# The geometry for our basic buttons
maps = loader.loadModel("models/button_map")
self.buttonGeom = (
maps.find("**/ready"),
maps.find("**/click"),
maps.find("**/hover"),
maps.find("**/disabled"))
# Create the dialog that asks the user for input on a given
# action to map a key to.
DGG.setDefaultDialogGeom("models/dialog.png")
# setup a dialog to ask for device input
self.dlgInput = OkCancelDialog(
dialogName="dlg_device_input",
pos=(0, 0, 0.25),
text="Hit desired key:",
text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
text_shadow=VBase4(0, 0, 0, 0.75),
text_shadowOffset=Vec2(0.05, 0.05),
text_scale=0.05,
text_align=TextNode.ACenter,
fadeScreen=0.65,
frameColor=VBase4(0.3, 0.3, 0.3, 1),
button_geom=self.buttonGeom,
button_scale=0.15,
button_text_scale=0.35,
button_text_align=TextNode.ALeft,
button_text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
button_text_pos=Vec2(-0.9, -0.125),
button_relief=1,
button_pad=Vec2(0.01, 0.01),
button_frameColor=VBase4(0, 0, 0, 0),
button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25),
button_pressEffect=False,
command=self.closeDialog)
self.dlgInput.setTransparency(True)
self.dlgInput.configureDialog()
scale = self.dlgInput["image_scale"]
self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0)
self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06)
self.dlgInput.hide()
# create a sample title
self.textscale = 0.1
self.title = DirectLabel(
scale=self.textscale,
pos=(base.a2dLeft + 0.05, 0.0, base.a2dTop - (self.textscale + 0.05)),
frameColor=VBase4(0, 0, 0, 0),
text="Button Mapping",
text_align=TextNode.ALeft,
text_fg=VBase4(1, 1, 1, 1),
text_shadow=VBase4(0, 0, 0, 0.75),
text_shadowOffset=Vec2(0.05, 0.05))
self.title.setTransparency(1)
# Set up the list of actions that we can map keys to
# create a frame that will create the scrollbars for us
# Load the models for the scrollbar elements
thumbMaps = loader.loadModel("models/thumb_map")
thumbGeom = (
thumbMaps.find("**/thumb_ready"),
thumbMaps.find("**/thumb_click"),
thumbMaps.find("**/thumb_hover"),
thumbMaps.find("**/thumb_disabled"))
incMaps = loader.loadModel("models/inc_map")
incGeom = (
incMaps.find("**/inc_ready"),
incMaps.find("**/inc_click"),
incMaps.find("**/inc_hover"),
incMaps.find("**/inc_disabled"))
decMaps = loader.loadModel("models/dec_map")
decGeom = (
decMaps.find("**/dec_ready"),
decMaps.find("**/dec_click"),
decMaps.find("**/dec_hover"),
decMaps.find("**/dec_disabled"))
# create the scrolled frame that will hold our list
self.lstActionMap = DirectScrolledFrame(
# make the frame occupy the whole window
frameSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 1.55),
# make the canvas as big as the frame
canvasSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 0.0),
# set the frames color to white
frameColor=VBase4(0, 0, 0.25, 0.75),
pos=(0, 0, -0.8),
verticalScroll_scrollSize=0.2,
verticalScroll_frameColor=VBase4(0.02, 0.02, 0.02, 1),
verticalScroll_thumb_relief=1,
verticalScroll_thumb_geom=thumbGeom,
verticalScroll_thumb_pressEffect=False,
verticalScroll_thumb_frameColor=VBase4(0, 0, 0, 0),
verticalScroll_incButton_relief=1,
verticalScroll_incButton_geom=incGeom,
verticalScroll_incButton_pressEffect=False,
verticalScroll_incButton_frameColor=VBase4(0, 0, 0, 0),
verticalScroll_decButton_relief=1,
verticalScroll_decButton_geom=decGeom,
verticalScroll_decButton_pressEffect=False,
verticalScroll_decButton_frameColor=VBase4(0, 0, 0, 0),)
# creat the list items
idx = 0
self.listBGEven = base.loader.loadModel("models/list_item_even")
self.listBGOdd = base.loader.loadModel("models/list_item_odd")
for key, value in self.gamepadMapping.items():
item = self.__makeListItem(key, key, value, idx)
item.reparentTo(self.lstActionMap.getCanvas())
idx += 1
# recalculate the canvas size to set scrollbars if necesary
self.lstActionMap["canvasSize"] = (
base.a2dLeft+0.05, base.a2dRight-0.05,
-(len(self.gamepadMapping.keys())*0.1), 0.09)
self.lstActionMap.setCanvasSize()
def closeDialog(self, result):
self.dlgInput.hide()
if result == DGG.DIALOG_OK:
# map the event to the given action
self.gamepadMapping[self.actionToMap] = self.newActionKey
# actualize the label in the list that shows the current
# event for the action
self.actualizeLabel["text"] = self.newActionKey
# cleanup
self.dlgInput["text"] ="Hit desired key:"
self.actionToMap = ""
self.newActionKey = ""
self.actualizeLabel = None
for bt in base.buttonThrowers:
bt.node().setButtonDownEvent("")
for bt in base.deviceButtonThrowers:
bt.node().setButtonDownEvent("")
taskMgr.remove("checkControls")
def changeMapping(self, action, label):
# set the action that we want to map a new key to
self.actionToMap = action
# set the label that needs to be actualized
self.actualizeLabel = label
# show our dialog
self.dlgInput.show()
# catch all button events
for bt in base.buttonThrowers:
bt.node().setButtonDownEvent("keyListenEvent")
for bt in base.deviceButtonThrowers:
bt.node().setButtonDownEvent("deviceListenEvent")
self.setKeyCalled = False
self.accept("keyListenEvent", self.setKey)
self.accept("deviceListenEvent", self.setDeviceKey)
# As there are no events thrown for control changes, we set up
# a task to check if the controls got moved
# This list will help us for checking which controls got moved
self.controlStates = {None:{}}
# fill it with all available controls
for device in base.devices.get_devices():
for ctrl in device.controls:
if device not in self.controlStates.keys():
self.controlStates.update({device: {ctrl.axis: ctrl.state}})
else:
self.controlStates[device].update({ctrl.axis: ctrl.state})
# start the task
taskMgr.add(self.watchControls, "checkControls")
def watchControls(self, task):
# move through all devices and all it's controls
for device in base.devices.get_devices():
for ctrl in device.controls:
# if a control got changed more than the given puffer zone
if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \
self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state:
# set the current state in the dict
self.controlStates[device][ctrl.axis] = ctrl.state
# check which axis got moved
if ctrl.axis == InputDevice.C_left_x:
self.setKey("Left Stick X")
elif ctrl.axis == InputDevice.C_left_y:
self.setKey("Left Stick Y")
elif ctrl.axis == InputDevice.C_left_trigger:
self.setKey("Left Trigger")
elif ctrl.axis == InputDevice.C_right_x:
self.setKey("Right Stick X")
elif ctrl.axis == InputDevice.C_right_y:
self.setKey("Right Stick Y")
elif ctrl.axis == InputDevice.C_right_trigger:
self.setKey("Right Trigger")
elif ctrl.axis == InputDevice.C_x:
self.setKey("X")
elif ctrl.axis == InputDevice.C_y:
self.setKey("Y")
elif ctrl.axis == InputDevice.C_trigger:
self.setKey("Trigger")
elif ctrl.axis == InputDevice.C_throttle:
self.setKey("Throttle")
elif ctrl.axis == InputDevice.C_rudder:
self.setKey("Rudder")
elif ctrl.axis == InputDevice.C_hat_x:
self.setKey("Hat X")
elif ctrl.axis == InputDevice.C_hat_y:
self.setKey("Hat Y")
elif ctrl.axis == InputDevice.C_wheel:
self.setKey("Wheel")
elif ctrl.axis == InputDevice.C_accelerator:
self.setKey("Acclerator")
elif ctrl.axis == InputDevice.C_brake:
self.setKey("Break")
return task.cont
def setKey(self, args):
self.setKeyCalled = True
if self.dlgInput.buttonList[0].guiItem.getState() == 1:
# this occurs if the OK button was clicked. To prevent to
# always set the mouse1 event whenever the OK button was
# pressed, we instantly return from this function
return
self.dlgInput["text"] = "New event will be:\n\n" + args
self.newActionKey = args
def setDeviceKey(self, args):
if not self.setKeyCalled:
self.setKey(args)
self.setKeyCalled = False
def __makeListItem(self, itemName, action, event, index):
def dummy(): pass
if index % 2 == 0:
bg = self.listBGEven
else:
bg = self.listBGOdd
item = DirectFrame(
text=itemName,
geom=bg,
geom_scale=(base.a2dRight-0.05, 1, 0.1),
frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05),
frameColor=VBase4(1,0,0,0),
text_align=TextNode.ALeft,
text_scale=0.05,
text_fg=VBase4(1,1,1,1),
text_pos=(base.a2dLeft + 0.3, -0.015),
text_shadow=VBase4(0, 0, 0, 0.35),
text_shadowOffset=Vec2(-0.05, -0.05),
pos=(0.05, 0, -(0.10 * index)))
item.setTransparency(True)
lbl = DirectLabel(
text=event,
text_fg=VBase4(1, 1, 1, 1),
text_scale=0.05,
text_pos=Vec2(0, -0.015),
frameColor=VBase4(0, 0, 0, 0),
)
lbl.reparentTo(item)
lbl.setTransparency(True)
buttonScale = 0.15
btn = DirectButton(
text="Change",
geom=self.buttonGeom,
scale=buttonScale,
text_scale=0.25,
text_align=TextNode.ALeft,
text_fg=VBase4(0.898, 0.839, 0.730, 1.0),
text_pos=Vec2(-0.9, -0.085),
relief=1,
pad=Vec2(0.01, 0.01),
frameColor=VBase4(0, 0, 0, 0),
frameSize=VBase4(-1.0, 1.0, -0.25, 0.25),
pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0),
pressEffect=False,
command=self.changeMapping,
extraArgs=[action, lbl])
btn.setTransparency(True)
btn.reparentTo(item)
return item
app = App()
app.run()

View File

@ -0,0 +1,117 @@
<Comment> {
"egg-texture-cards -o button_map.egg -p 768,192 -g -1.0,1.0,-0.25,0.25 -wm clamp ready.png click.png hover.png disabled.png"
}
<Texture> ready {
ready.png
<Scalar> wrap { clamp }
}
<Texture> hover {
hover.png
<Scalar> wrap { clamp }
}
<Texture> disabled {
disabled.png
<Scalar> wrap { clamp }
}
<Texture> click {
click.png
<Scalar> wrap { clamp }
}
<Group> {
<Switch> { 1 }
<Scalar> fps { 2 }
<VertexPool> vpool {
<Vertex> 0 {
-1 0.25 0
<UV> { 0 1 }
}
<Vertex> 1 {
-1 -0.25 0
<UV> { 0 0 }
}
<Vertex> 2 {
1 -0.25 0
<UV> { 1 0 }
}
<Vertex> 3 {
1 0.25 0
<UV> { 1 1 }
}
<Vertex> 4 {
-1 0.25 0
<UV> { 0 1 }
}
<Vertex> 5 {
-1 -0.25 0
<UV> { 0 0 }
}
<Vertex> 6 {
1 -0.25 0
<UV> { 1 0 }
}
<Vertex> 7 {
1 0.25 0
<UV> { 1 1 }
}
<Vertex> 8 {
-1 0.25 0
<UV> { 0 1 }
}
<Vertex> 9 {
-1 -0.25 0
<UV> { 0 0 }
}
<Vertex> 10 {
1 -0.25 0
<UV> { 1 0 }
}
<Vertex> 11 {
1 0.25 0
<UV> { 1 1 }
}
<Vertex> 12 {
-1 0.25 0
<UV> { 0 1 }
}
<Vertex> 13 {
-1 -0.25 0
<UV> { 0 0 }
}
<Vertex> 14 {
1 -0.25 0
<UV> { 1 0 }
}
<Vertex> 15 {
1 0.25 0
<UV> { 1 1 }
}
}
<Group> ready {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { ready }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
<Group> click {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { click }
<VertexRef> { 4 5 6 7 <Ref> { vpool } }
}
}
<Group> hover {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { hover }
<VertexRef> { 8 9 10 11 <Ref> { vpool } }
}
}
<Group> disabled {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { disabled }
<VertexRef> { 12 13 14 15 <Ref> { vpool } }
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

View File

@ -0,0 +1,117 @@
<Comment> {
"egg-texture-cards -o dec_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp dec_ready.png dec_click.png dec_hover.png dec_disabled.png"
}
<Texture> dec_ready {
dec_ready.png
<Scalar> wrap { clamp }
}
<Texture> dec_hover {
dec_hover.png
<Scalar> wrap { clamp }
}
<Texture> dec_disabled {
dec_disabled.png
<Scalar> wrap { clamp }
}
<Texture> dec_click {
dec_click.png
<Scalar> wrap { clamp }
}
<Group> {
<Switch> { 1 }
<Scalar> fps { 2 }
<VertexPool> vpool {
<Vertex> 0 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 1 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 2 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 3 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 4 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 5 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 6 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 7 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 8 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 9 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 10 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 11 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 12 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 13 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 14 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 15 {
0.04 0.04 0
<UV> { 1 1 }
}
}
<Group> dec_ready {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { dec_ready }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
<Group> dec_click {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { dec_click }
<VertexRef> { 4 5 6 7 <Ref> { vpool } }
}
}
<Group> dec_hover {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { dec_hover }
<VertexRef> { 8 9 10 11 <Ref> { vpool } }
}
}
<Group> dec_disabled {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { dec_disabled }
<VertexRef> { 12 13 14 15 <Ref> { vpool } }
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

View File

@ -0,0 +1,117 @@
<Comment> {
"egg-texture-cards -o inc_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp inc_ready.png inc_click.png inc_hover.png inc_disabled.png"
}
<Texture> inc_ready {
inc_ready.png
<Scalar> wrap { clamp }
}
<Texture> inc_hover {
inc_hover.png
<Scalar> wrap { clamp }
}
<Texture> inc_disabled {
inc_disabled.png
<Scalar> wrap { clamp }
}
<Texture> inc_click {
inc_click.png
<Scalar> wrap { clamp }
}
<Group> {
<Switch> { 1 }
<Scalar> fps { 2 }
<VertexPool> vpool {
<Vertex> 0 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 1 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 2 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 3 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 4 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 5 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 6 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 7 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 8 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 9 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 10 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 11 {
0.04 0.04 0
<UV> { 1 1 }
}
<Vertex> 12 {
-0.04 0.04 0
<UV> { 0 1 }
}
<Vertex> 13 {
-0.04 -0.04 0
<UV> { 0 0 }
}
<Vertex> 14 {
0.04 -0.04 0
<UV> { 1 0 }
}
<Vertex> 15 {
0.04 0.04 0
<UV> { 1 1 }
}
}
<Group> inc_ready {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { inc_ready }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
<Group> inc_click {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { inc_click }
<VertexRef> { 4 5 6 7 <Ref> { vpool } }
}
}
<Group> inc_hover {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { inc_hover }
<VertexRef> { 8 9 10 11 <Ref> { vpool } }
}
}
<Group> inc_disabled {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { inc_disabled }
<VertexRef> { 12 13 14 15 <Ref> { vpool } }
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

@ -0,0 +1,33 @@
<Comment> {
"egg-texture-cards -o list_item_even.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_even.png"
}
<Texture> li_ready_even {
li_ready_even.png
}
<Group> {
<VertexPool> vpool {
<Vertex> 0 {
-1 0.5 0
<UV> { 0 1 }
}
<Vertex> 1 {
-1 -0.5 0
<UV> { 0 0 }
}
<Vertex> 2 {
1 -0.5 0
<UV> { 1 0 }
}
<Vertex> 3 {
1 0.5 0
<UV> { 1 1 }
}
}
<Group> li_ready_even {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { li_ready_even }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
}

View File

@ -0,0 +1,33 @@
<Comment> {
"egg-texture-cards -o list_item_odd.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_odd.png"
}
<Texture> li_ready_odd {
li_ready_odd.png
}
<Group> {
<VertexPool> vpool {
<Vertex> 0 {
-1 0.5 0
<UV> { 0 1 }
}
<Vertex> 1 {
-1 -0.5 0
<UV> { 0 0 }
}
<Vertex> 2 {
1 -0.5 0
<UV> { 1 0 }
}
<Vertex> 3 {
1 0.5 0
<UV> { 1 1 }
}
}
<Group> li_ready_odd {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { li_ready_odd }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

View File

@ -0,0 +1,117 @@
<Comment> {
"egg-texture-cards -o thumb_map.egg -p 32,440 -g -0.04,0.04,-0.55,0.55 -wm clamp thumb_ready.png thumb_click.png thumb_hover.png thumb_disabled.png"
}
<Texture> thumb_ready {
thumb_ready.png
<Scalar> wrap { clamp }
}
<Texture> thumb_hover {
thumb_hover.png
<Scalar> wrap { clamp }
}
<Texture> thumb_disabled {
thumb_disabled.png
<Scalar> wrap { clamp }
}
<Texture> thumb_click {
thumb_click.png
<Scalar> wrap { clamp }
}
<Group> {
<Switch> { 1 }
<Scalar> fps { 2 }
<VertexPool> vpool {
<Vertex> 0 {
-0.04 0.55 0
<UV> { 0 1 }
}
<Vertex> 1 {
-0.04 -0.55 0
<UV> { 0 0 }
}
<Vertex> 2 {
0.04 -0.55 0
<UV> { 1 0 }
}
<Vertex> 3 {
0.04 0.55 0
<UV> { 1 1 }
}
<Vertex> 4 {
-0.04 0.55 0
<UV> { 0 1 }
}
<Vertex> 5 {
-0.04 -0.55 0
<UV> { 0 0 }
}
<Vertex> 6 {
0.04 -0.55 0
<UV> { 1 0 }
}
<Vertex> 7 {
0.04 0.55 0
<UV> { 1 1 }
}
<Vertex> 8 {
-0.04 0.55 0
<UV> { 0 1 }
}
<Vertex> 9 {
-0.04 -0.55 0
<UV> { 0 0 }
}
<Vertex> 10 {
0.04 -0.55 0
<UV> { 1 0 }
}
<Vertex> 11 {
0.04 0.55 0
<UV> { 1 1 }
}
<Vertex> 12 {
-0.04 0.55 0
<UV> { 0 1 }
}
<Vertex> 13 {
-0.04 -0.55 0
<UV> { 0 0 }
}
<Vertex> 14 {
0.04 -0.55 0
<UV> { 1 0 }
}
<Vertex> 15 {
0.04 0.55 0
<UV> { 1 1 }
}
}
<Group> thumb_ready {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { thumb_ready }
<VertexRef> { 0 1 2 3 <Ref> { vpool } }
}
}
<Group> thumb_click {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { thumb_click }
<VertexRef> { 4 5 6 7 <Ref> { vpool } }
}
}
<Group> thumb_hover {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { thumb_hover }
<VertexRef> { 8 9 10 11 <Ref> { vpool } }
}
}
<Group> thumb_disabled {
<Polygon> {
<RGBA> { 1 1 1 1 }
<TRef> { thumb_disabled }
<VertexRef> { 12 13 14 15 <Ref> { vpool } }
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

View File

@ -0,0 +1,140 @@
#!/usr/bin/env python
'''
Demonstrate usage of steering wheels
In this sample you can use a wheel type device to control the camera and
show some messages on screen. You can acclerate forward using the
accleration pedal and slow down using the break pedal.
'''
from direct.showbase.ShowBase import ShowBase
from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3
from direct.gui.OnscreenText import OnscreenText
loadPrcFileData("", "notify-level-device debug")
class App(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# print all events sent through the messenger
self.messenger.toggleVerbose()
self.lblWarning = OnscreenText(
text = "No devices found",
fg=(1,0,0,1),
scale = .25)
self.lblWarning.hide()
self.lblAction = OnscreenText(
text = "Action",
fg=(1,1,1,1),
scale = .15)
self.lblAction.hide()
self.checkDevices()
self.currentMoveSpeed = 0.0
self.maxAccleration = 28.0
self.deaccleration = 10.0
self.deaclerationBreak = 37.0
self.maxSpeed = 80.0
# Accept device dis-/connection events
# NOTE: catching the events here will overwrite the accept in showbase, hence
# we need to forward the event in the functions we set here!
self.accept("connect-device", self.connect)
self.accept("disconnect-device", self.disconnect)
self.accept("escape", exit)
self.accept("flight_stick0-start", exit)
# Accept button events of the first connected steering wheel
self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"])
self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"])
self.environment = loader.loadModel("environment")
self.environment.reparentTo(render)
# save the center position of the wheel
# NOTE: here we assume, that the wheel is centered when the application get started.
# In real world applications, you should notice the user and give him enough time
# to center the wheel until you store the center position of the controler!
self.wheelCenter = 0
wheels = base.devices.getDevices(InputDevice.DC_steering_wheel)
if len(wheels) > 0:
self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state
# disable pandas default mouse-camera controls so we can handle the camera
# movements by ourself
self.disableMouse()
base.camera.setZ(2)
self.taskMgr.add(self.moveTask, "movement update task")
def connect(self, device):
# we need to forward the event to the connectDevice function of showbase
self.connectDevice(device)
# Now we can check for ourself
self.checkDevices()
def disconnect(self, device):
# we need to forward the event to the disconnectDevice function of showbase
self.disconnectDevice(device)
# Now we can check for ourself
self.checkDevices()
def checkDevices(self):
# check if we have wheel devices connected
if self.devices.get_devices(InputDevice.DC_steering_wheel):
# we have at least one steering wheel device
self.lblWarning.hide()
else:
# no devices connected
self.lblWarning.show()
def doAction(self, showText, text):
if showText and self.lblAction.isHidden():
self.lblAction.show()
else:
self.lblAction.hide()
def moveTask(self, task):
dt = globalClock.getDt()
movementVec = Vec3()
wheels = base.devices.getDevices(InputDevice.DC_steering_wheel)
if len(wheels) == 0:
# savety check
return task.cont
if self.currentMoveSpeed > 0:
self.currentMoveSpeed -= dt * self.deaccleration
if self.currentMoveSpeed < 0:
self.currentMoveSpeed = 0
# we will use the first found wheel
# Acclerate
acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state
accleration = accleratorPedal * self.maxAccleration
if self.currentMoveSpeed > accleratorPedal * self.maxSpeed:
self.currentMoveSpeed -= dt * self.deaccleration
self.currentMoveSpeed += dt * accleration
# Break
breakPedal = wheels[0].findControl(InputDevice.C_brake).state
deacleration = breakPedal * self.deaclerationBreak
self.currentMoveSpeed -= dt * deacleration
if self.currentMoveSpeed < 0:
self.currentMoveSpeed = 0
# Steering
rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state
base.camera.setH(base.camera, 100 * dt * rotation)
# calculate movement
base.camera.setY(base.camera, dt * self.currentMoveSpeed)
return task.cont
app = App()
app.run()