Merge branch 'input-overhaul' of github.com:grimfang/panda3d into input-overhaul
@ -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).
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
71
panda/src/device/inputDeviceNode.cxx
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
66
panda/src/device/inputDeviceNode.h
Normal 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
|
@ -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.
|
||||
|
@ -10,3 +10,4 @@
|
||||
#include "inputDeviceManager.cxx"
|
||||
#include "inputDeviceSet.cxx"
|
||||
#include "linuxJoystickDevice.cxx"
|
||||
#include "inputDeviceNode.cxx"
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
@ -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()
|
342
samples/gamepad/mappingGUI.py
Normal 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()
|
117
samples/gamepad/models/button_map.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/click.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
samples/gamepad/models/dec_click.png
Normal file
After Width: | Height: | Size: 847 B |
BIN
samples/gamepad/models/dec_disabled.png
Normal file
After Width: | Height: | Size: 852 B |
BIN
samples/gamepad/models/dec_hover.png
Normal file
After Width: | Height: | Size: 831 B |
117
samples/gamepad/models/dec_map.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/dec_ready.png
Normal file
After Width: | Height: | Size: 781 B |
BIN
samples/gamepad/models/dialog.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
samples/gamepad/models/disabled.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
samples/gamepad/models/hover.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
samples/gamepad/models/inc_click.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
samples/gamepad/models/inc_disabled.png
Normal file
After Width: | Height: | Size: 866 B |
BIN
samples/gamepad/models/inc_hover.png
Normal file
After Width: | Height: | Size: 835 B |
117
samples/gamepad/models/inc_map.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/inc_ready.png
Normal file
After Width: | Height: | Size: 794 B |
BIN
samples/gamepad/models/li_ready_even.png
Normal file
After Width: | Height: | Size: 186 B |
BIN
samples/gamepad/models/li_ready_odd.png
Normal file
After Width: | Height: | Size: 186 B |
33
samples/gamepad/models/list_item_even.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
33
samples/gamepad/models/list_item_odd.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/ready.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
samples/gamepad/models/thumb_click.png
Normal file
After Width: | Height: | Size: 916 B |
BIN
samples/gamepad/models/thumb_disabled.png
Normal file
After Width: | Height: | Size: 912 B |
BIN
samples/gamepad/models/thumb_hover.png
Normal file
After Width: | Height: | Size: 892 B |
117
samples/gamepad/models/thumb_map.egg
Normal 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 } }
|
||||
}
|
||||
}
|
||||
}
|
BIN
samples/gamepad/models/thumb_ready.png
Normal file
After Width: | Height: | Size: 815 B |
140
samples/gamepad/steeringWheel.py
Normal 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()
|