2021-04-14 23:52:13 -07:00

1074 lines
35 KiB
C++

// Released under the MIT License. See LICENSE for details.
#include "ballistica/input/device/touch_input.h"
#include "ballistica/app/app_config.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/player.h"
#include "ballistica/graphics/camera.h"
#include "ballistica/graphics/component/simple_component.h"
#include "ballistica/python/python.h"
#include "ballistica/scene/node/player_node.h"
#include "ballistica/ui/ui.h"
namespace ballistica {
const float kButtonSpread = 10.0f;
const float kDrawDepth = -0.07f;
// Given coords within a (-1,-1) to (1,1) box,
// convert them such that their length is never greater than 1.
static void CircleToBoxCoords(float* lr, float* ud) {
if (std::abs((*lr)) < 0.0001f || std::abs((*ud)) < 0.0001f) {
return; // Not worth doing anything.
}
// Project them out to hit the border.
float s;
if (std::abs((*lr)) > std::abs((*ud))) {
s = 1.0f / std::abs((*lr));
} else {
s = 1.0f / std::abs((*ud));
}
float proj_lr = (*lr) * s;
float proj_ud = (*ud) * s;
float proj_len = sqrtf(proj_lr * proj_lr + proj_ud * proj_ud);
(*lr) *= proj_len;
(*ud) *= proj_len;
}
void TouchInput::HandleTouchEvent(TouchEvent::Type type, void* touch, float x,
float y) {
// Currently we completely ignore these when in editing mode;
// In that case we get fed in SDL mouse events
// (so we can properly mask interaction with widgets, etc).
if (editing()) {
return;
}
switch (type) {
case TouchEvent::Type::kDown: {
HandleTouchDown(touch, x, y);
break;
}
case TouchEvent::Type::kCanceled:
case TouchEvent::Type::kUp: {
HandleTouchUp(touch, x, y);
break;
}
case TouchEvent::Type::kMoved: {
HandleTouchMoved(touch, x, y);
break;
}
default:
throw Exception();
}
}
TouchInput::TouchInput() {
switch (GetUIScale()) {
case UIScale::kSmall:
base_controls_scale_ = 2.0f;
world_draw_scale_ = 1.2f;
break;
case UIScale::kMedium:
base_controls_scale_ = 1.5f;
world_draw_scale_ = 1.1f;
break;
default:
base_controls_scale_ = 1.0f;
world_draw_scale_ = 1.0f;
break;
}
assert(g_app_globals->touch_input == nullptr);
g_app_globals->touch_input = this;
}
TouchInput::~TouchInput() = default;
void TouchInput::UpdateButtons(bool new_touch) {
millisecs_t real_time = GetRealTime();
float spread_scaled_actions =
kButtonSpread * base_controls_scale_ * controls_scale_actions_;
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
float edge_buffer = spread_scaled_actions;
if (new_touch && action_control_type_ == ActionControlType::kSwipe) {
buttons_x_ = buttons_touch_x_;
buttons_y_ = buttons_touch_y_;
}
// See which button we're closest to.
float bomb_mag = buttons_touch_x_ - buttons_x_;
float punch_mag = buttons_x_ - buttons_touch_x_;
float jump_mag = buttons_y_ - buttons_touch_y_;
float pickup_mag = buttons_touch_y_ - buttons_y_;
float max_mag =
std::max(std::max(std::max(bomb_mag, punch_mag), jump_mag), pickup_mag);
bool closest_to_bomb = false;
bool closest_to_punch = false;
bool closest_to_jump = false;
bool closest_to_pickup = false;
if (bomb_mag == max_mag) {
closest_to_bomb = true;
} else if (punch_mag == max_mag) {
closest_to_punch = true;
} else if (jump_mag == max_mag) {
closest_to_jump = true;
} else if (pickup_mag == max_mag) {
closest_to_pickup = true;
} else {
throw Exception();
}
if (buttons_touch_) {
last_buttons_touch_time_ = GetRealTime();
}
// Handle swipe mode.
if (action_control_type_ == ActionControlType::kSwipe) {
// If we're dragging on one axis, center the other axis.
if (closest_to_bomb
// NOLINTNEXTLINE(bugprone-branch-clone)
&& buttons_touch_x_ >= buttons_x_ + spread_scaled_actions) {
buttons_y_ = buttons_touch_y_;
} else if (closest_to_punch
&& buttons_touch_x_ <= buttons_x_ - spread_scaled_actions) {
buttons_y_ = buttons_touch_y_;
} else if (closest_to_pickup
// NOLINTNEXTLINE(bugprone-branch-clone)
&& buttons_touch_y_ >= buttons_y_ + spread_scaled_actions) {
buttons_x_ = buttons_touch_x_;
} else if (closest_to_jump
&& buttons_touch_y_ <= buttons_y_ - spread_scaled_actions) {
buttons_x_ = buttons_touch_x_;
}
// Drag along the axis we're dragging.
float spread_scaled_actions_extra = 1.01f * spread_scaled_actions;
if (closest_to_bomb
&& buttons_touch_x_ >= buttons_x_ + spread_scaled_actions_extra) {
buttons_x_ = buttons_touch_x_ - spread_scaled_actions_extra;
} else if (closest_to_punch
&& buttons_touch_x_
<= buttons_x_ - spread_scaled_actions_extra) {
buttons_x_ = buttons_touch_x_ + spread_scaled_actions_extra;
} else if (closest_to_pickup
&& buttons_touch_y_
>= buttons_y_ + spread_scaled_actions_extra) {
buttons_y_ = buttons_touch_y_ - spread_scaled_actions_extra;
} else if (closest_to_jump
&& buttons_touch_y_
<= buttons_y_ - spread_scaled_actions_extra) {
buttons_y_ = buttons_touch_y_ + spread_scaled_actions_extra;
}
// Keep them away from screen edges.
if (buttons_x_ > width - edge_buffer) {
buttons_x_ = width - edge_buffer;
}
if (buttons_y_ > height - edge_buffer) {
buttons_y_ = height - edge_buffer;
} else if (buttons_y_ < edge_buffer) {
buttons_y_ = edge_buffer;
}
// Handle new presses.
if (buttons_touch_) {
if (!bomb_held_
&& buttons_touch_x_ >= buttons_x_ + spread_scaled_actions) {
bomb_held_ = true;
last_bomb_press_time_ = real_time;
InputCommand(InputType::kBombPress);
}
if (!punch_held_
&& buttons_touch_x_ <= buttons_x_ - spread_scaled_actions) {
punch_held_ = true;
last_punch_press_time_ = real_time;
InputCommand(InputType::kPunchPress);
}
if (!jump_held_
&& buttons_touch_y_ <= buttons_y_ - spread_scaled_actions) {
jump_held_ = true;
last_jump_press_time_ = real_time;
InputCommand(InputType::kJumpPress);
}
if (!pickup_held_
&& buttons_touch_y_ >= buttons_y_ + spread_scaled_actions) {
pickup_held_ = true;
last_pickup_press_time_ = real_time;
InputCommand(InputType::kPickUpPress);
}
}
// Handle releases.
if (bomb_held_
&& (!buttons_touch_
|| buttons_touch_x_ < buttons_x_ + spread_scaled_actions)) {
bomb_held_ = false;
last_bomb_held_time_ = real_time;
InputCommand(InputType::kBombRelease);
}
if (punch_held_
&& (!buttons_touch_
|| buttons_touch_x_ > buttons_x_ - spread_scaled_actions)) {
punch_held_ = false;
last_punch_held_time_ = real_time;
InputCommand(InputType::kPunchRelease);
}
if (jump_held_
&& (!buttons_touch_
|| buttons_touch_y_ > buttons_y_ - spread_scaled_actions)) {
jump_held_ = false;
last_jump_held_time_ = real_time;
InputCommand(InputType::kJumpRelease);
}
if (pickup_held_
&& (!buttons_touch_
|| buttons_touch_y_ < buttons_y_ + spread_scaled_actions)) {
pickup_held_ = false;
last_pickup_held_time_ = real_time;
InputCommand(InputType::kPickUpRelease);
}
} else {
bool was_bomb_held = bomb_held_;
bool was_jump_held = jump_held_;
bool was_pickup_held = pickup_held_;
bool was_punch_held = punch_held_;
bomb_held_ = jump_held_ = pickup_held_ = punch_held_ = false;
if (buttons_touch_) {
if (closest_to_bomb) {
bomb_held_ = true;
if (!was_bomb_held) {
last_bomb_press_time_ = real_time;
InputCommand(InputType::kBombPress);
}
} else if (closest_to_punch) {
punch_held_ = true;
if (!was_punch_held) {
last_punch_press_time_ = real_time;
InputCommand(InputType::kPunchPress);
}
} else if (closest_to_jump) {
jump_held_ = true;
if (!was_jump_held) {
last_jump_press_time_ = real_time;
// fixme should just send one or the other..
InputCommand(InputType::kJumpPress);
InputCommand(InputType::kFlyPress);
}
} else if (closest_to_pickup) {
pickup_held_ = true;
if (!was_pickup_held) {
last_pickup_press_time_ = real_time;
InputCommand(InputType::kPickUpPress);
}
}
}
// Handle releases.
if (was_bomb_held && !bomb_held_) {
last_bomb_held_time_ = real_time;
InputCommand(InputType::kBombRelease);
}
if (was_punch_held && !punch_held_) {
punch_held_ = false;
last_punch_held_time_ = real_time;
InputCommand(InputType::kPunchRelease);
}
if (was_jump_held && !jump_held_) {
jump_held_ = false;
last_jump_held_time_ = real_time;
// fixme should just send one or the other..
InputCommand(InputType::kJumpRelease);
InputCommand(InputType::kFlyRelease);
}
if (was_pickup_held && !pickup_held_) {
pickup_held_ = false;
last_pickup_held_time_ = real_time;
InputCommand(InputType::kPickUpRelease);
}
}
}
void TouchInput::UpdateDPad() {
// Keep our base somewhat close to our drag point.
float max_dist = 30.0f * base_controls_scale_ * controls_scale_move_;
// Keep it within a circle of max_dist radius.
float x = (d_pad_x_ - d_pad_base_x_) / max_dist;
float y = (d_pad_y_ - d_pad_base_y_) / max_dist;
float len = sqrtf(x * x + y * y);
// In swipe mode we move our base around to follow the touch.
if (movement_control_type_ == MovementControlType::kSwipe) {
// If this is the first move event, scoot our base towards our current point
// by a small amount. This is meant to counter the fact that the first
// touch-moved event is always significantly far from the touch-down and
// allows us to start out moving slowly.
if (!did_first_move_ && (x != 0 || y != 0)) {
if (len != 0.0f) {
float offs = 0.8f * std::min(len, 0.8f);
d_pad_base_x_ += x * max_dist * (offs / len);
d_pad_base_y_ += y * max_dist * (offs / len);
x = (d_pad_x_ - d_pad_base_x_) / max_dist;
y = (d_pad_y_ - d_pad_base_y_) / max_dist;
len = sqrtf(x * x + y * y);
}
did_first_move_ = true;
}
if (len > 1.0f) {
float inv_len = 1.0f / len;
x *= inv_len;
y *= inv_len;
d_pad_base_x_ = d_pad_x_ - x * max_dist;
d_pad_base_y_ = d_pad_y_ - y * max_dist;
}
} else {
// Likewise in joystick mode we keep our touch near the base.
if (len > 1.0f) {
float inv_len = 1.0f / len;
x *= inv_len;
y *= inv_len;
d_pad_x_ = d_pad_base_x_ + x * max_dist;
d_pad_y_ = d_pad_base_y_ + y * max_dist;
}
}
d_pad_draw_x_ = x;
d_pad_draw_y_ = y;
// Although its a circle we need to deliver box coords.. (ie: upper-left is
// -1,1).
CircleToBoxCoords(&x, &y);
float remap = 1.0f;
InputCommand(InputType::kLeftRight, x * remap);
InputCommand(InputType::kUpDown, y * remap);
}
void TouchInput::Draw(FrameDef* frame_def) {
assert(InGameThread());
bool active = (!g_ui->IsWindowPresent());
millisecs_t real_time = frame_def->real_time();
// Update our action center whenever possible in case screen is resized.
if (!buttons_touch_) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
buttons_x_ = width * buttons_default_frac_x_;
buttons_y_ = height * buttons_default_frac_y_;
}
// Same for dpad.
if (!d_pad_touch_) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
d_pad_x_ = d_pad_base_x_ = width * d_pad_default_frac_x_;
d_pad_y_ = d_pad_base_y_ = height * d_pad_default_frac_y_;
}
// Update time-dependent stuff to this point.
if ((real_time - update_time_ > 500) && (real_time - update_time_ < 99999)) {
update_time_ = real_time - 500;
}
while (update_time_ < real_time) {
update_time_ += 10;
// Update presence based on whether or not we're active.
if ((attached_to_player() && active) || editing_) {
presence_ = std::min(1.0f, presence_ + 0.06f);
} else {
presence_ = std::max(0.0f, presence_ - 0.06f);
}
if (action_control_type_ == ActionControlType::kSwipe) {
// Overall backing opacity fades in and out based on whether we have a
// button touch.
if (buttons_touch_ || editing_) {
button_fade_ = std::min(1.0f, button_fade_ + 0.06f);
} else {
button_fade_ = std::max(0.0f, button_fade_ - 0.015f);
}
// If there's a button touch but its not on a button, slowly move the
// center towards it (keeps us from slowly sliding onto a button press
// while trying to run and stuff).
if (buttons_touch_ && !bomb_held_ && !punch_held_ && !pickup_held_
&& !jump_held_) {
buttons_x_ += 0.015f * (buttons_touch_x_ - buttons_x_);
buttons_y_ += 0.015f * (buttons_touch_y_ - buttons_y_);
}
} else {
button_fade_ = 1.0f;
}
}
if (presence_ > 0.0f) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
SimpleComponent c(frame_def->GetOverlayFlatPass());
c.SetTransparent(true);
float sc_move = base_controls_scale_ * controls_scale_move_
* (200.0f - presence_ * 100.0f);
float sc_actions = base_controls_scale_ * controls_scale_actions_
* (200.0f - presence_ * 100.0f);
bool do_draw;
if (movement_control_type_ == MovementControlType::kSwipe) {
do_draw = (!d_pad_touch_ && !swipe_controls_hidden_);
} else {
do_draw = true; // always draw in joystick mode
}
if (do_draw) {
float sc2 = sc_move;
if (movement_control_type_ == MovementControlType::kSwipe) sc2 *= 0.6f;
if (movement_control_type_ == MovementControlType::kSwipe) {
c.SetTexture(g_media->GetTexture(SystemTextureID::kTouchArrows));
if (editing_) {
float val = 1.5f + sinf(static_cast<float>(real_time) * 0.02f);
c.SetColor(val, val, 1.0f, 1.0f);
}
} else {
float val;
if (editing_) {
val = 0.35f + 0.15f * sinf(static_cast<float>(real_time) * 0.02f);
} else {
val = 0.35f;
}
c.SetColor(0.5f, 0.3f, 0.8f, val);
c.SetTexture(g_media->GetTexture(SystemTextureID::kCircle));
}
float x_offs =
width * (-0.1f - d_pad_default_frac_x_) * (1.0f - presence_);
float y_offs =
height * (-0.1f - d_pad_default_frac_y_) * (1.0f - presence_);
c.PushTransform();
c.Translate(d_pad_base_x_ + x_offs, d_pad_base_y_ + y_offs, kDrawDepth);
c.Scale(sc2, sc2);
c.DrawModel(g_media->GetModel(SystemModelID::kImage1x1));
c.PopTransform();
if (movement_control_type_ == MovementControlType::kJoystick) {
float val;
if (editing_) {
val = 0.35f + 0.15f * sinf(static_cast<float>(real_time) * 0.02f);
} else {
val = 0.35f;
}
c.SetColor(0.0f, 0.0f, 0.0f, val);
c.PushTransform();
c.Translate(d_pad_x_ + x_offs, d_pad_y_ + y_offs, kDrawDepth);
c.Scale(sc_move * 0.5f, sc_move * 0.5f);
c.DrawModel(g_media->GetModel(SystemModelID::kImage1x1));
c.PopTransform();
}
}
if (!buttons_touch_ && action_control_type_ == ActionControlType::kSwipe
&& !swipe_controls_hidden_) {
float sc2{sc_actions * 0.6f};
c.SetTexture(g_media->GetTexture(SystemTextureID::kTouchArrowsActions));
if (editing_) {
float val = 1.5f + sinf(static_cast<float>(real_time) * 0.02f);
c.SetColor(val, val, 1.0f, 1.0f);
} else {
c.SetColor(1.0f, 1.0f, 1.0f, 1.0f);
}
c.PushTransform();
float x_offs =
width * (1.1f - buttons_default_frac_x_) * (1.0f - presence_);
float y_offs =
height * (-0.1f - buttons_default_frac_y_) * (1.0f - presence_);
c.Translate(buttons_x_ + x_offs, buttons_y_ + y_offs, kDrawDepth);
c.Scale(sc2, sc2);
c.DrawModel(g_media->GetModel(SystemModelID::kImage1x1));
c.PopTransform();
}
c.Submit();
}
bool have_player_position{false};
std::vector<float> player_position(3);
if (attached_to_player()) {
PlayerNode* player_node{};
// Try to come up with whichever scene is in the foreground, and try
// to pull a node for the player we're attached to.
if (HostActivity* host_activity =
g_game->GetForegroundContext().GetHostActivity()) {
if (Player* player = GetPlayer()) {
player_node = host_activity->scene()->GetPlayerNode(player->id());
}
} else {
if (Scene* scene = g_game->GetForegroundScene()) {
player_node = scene->GetPlayerNode(remote_player_id());
}
}
if (player_node) {
have_player_position = true;
player_position = player_node->position();
}
}
SimpleComponent c(frame_def->GetOverlayFlatPass());
c.SetTransparent(true);
uint32_t residual_time{130};
// Draw buttons.
bool do_draw;
if (action_control_type_ == ActionControlType::kButtons) {
do_draw = (presence_ > 0.0f);
} else {
do_draw = (active);
}
if (do_draw) {
float base_fade;
if (action_control_type_ == ActionControlType::kSwipe) {
base_fade = 0.25f;
} else {
base_fade = 0.8f;
c.SetTexture(g_media->GetTexture(SystemTextureID::kActionButtons));
}
float x_offs;
float y_offs;
if (action_control_type_ == ActionControlType::kSwipe) {
x_offs = -buttons_x_;
y_offs = -buttons_y_ - 75;
} else {
x_offs = y_offs = 0.0f;
// Do transition in button mode.
if (presence_ < 1.0f) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
x_offs = width * (1.1f - buttons_default_frac_x_) * (1.0f - presence_);
y_offs =
height * (-0.1f - buttons_default_frac_y_) * (1.0f - presence_);
}
}
float s{0.5f};
// In buttons mode we draw based on our UI size. Otherwise we draw in the
// world at a constant scale.
if (action_control_type_ == ActionControlType::kButtons) {
s *= 3.0f * base_controls_scale_ * controls_scale_actions_;
} else {
// When not drawing under the character we obey ui size.
if (!have_player_position) {
s *= 0.5f * 1.5f * base_controls_scale_ * controls_scale_actions_;
} else {
s *= world_draw_scale_;
}
}
float b_width{50.0f * s};
float half_b_width{0.0f};
float button_spread_s{0.0f * s};
if (action_control_type_ == ActionControlType::kSwipe) {
button_spread_s *= 2.0f;
}
bool was_held;
float pop;
float pop_time{100.0f};
c.PushTransform();
// In swipe mode we draw under our character when possible, and above the
// touch otherwise.
if (action_control_type_ == ActionControlType::kSwipe) {
if (have_player_position) {
c.TranslateToProjectedPoint(player_position[0], player_position[1],
player_position[2]);
} else {
float s2 = base_controls_scale_ * controls_scale_actions_;
c.Translate(buttons_touch_start_x_ - s2 * 50.0f,
buttons_touch_start_y_ + 75.0f + s2 * 50.0f, 0.0f);
}
}
float squash{1.3f};
float stretch{1.3f};
float s_extra{1.0f};
if (editing_)
s_extra = 0.7f + 0.3f * sinf(static_cast<float>(real_time) * 0.02f);
// Bomb.
was_held =
bomb_held_ || (real_time - last_bomb_press_time_ < residual_time);
if ((button_fade_ > 0.0f) || bomb_held_ || was_held) {
pop = std::max(0.0f,
1.0f
- static_cast<float>(real_time - last_bomb_press_time_)
/ pop_time);
if (was_held) {
c.SetColor(1.5f, 2.0f * pop, 2.0f * pop, 1.0f);
} else {
c.SetColor(0.65f * s_extra, 0.0f, 0.0f, base_fade * button_fade_);
}
c.PushTransform();
c.Translate(buttons_x_ + button_spread_s + half_b_width + x_offs,
buttons_y_ + y_offs, kDrawDepth);
if (bomb_held_) {
c.Scale(stretch * b_width, squash * b_width);
} else {
c.Scale(b_width, b_width);
}
c.DrawModel(g_media->GetModel(SystemModelID::kActionButtonRight));
c.PopTransform();
}
// Punch.
was_held =
punch_held_ || (real_time - last_punch_press_time_ < residual_time);
if ((button_fade_ > 0.0f) || punch_held_ || was_held) {
pop = std::max(
0.0f, 1.0f
- static_cast<float>(real_time - last_punch_press_time_)
/ pop_time);
if (was_held) {
c.SetColor(1.3f + 2.0f * pop, 1.3f + 2.0f * pop, 0.0f + 2.0f * pop,
1.0f);
} else {
c.SetColor(0.9f * s_extra, 0.9f * s_extra, 0.2f * s_extra,
base_fade * button_fade_);
}
c.PushTransform();
c.Translate(buttons_x_ - button_spread_s - half_b_width + x_offs,
buttons_y_ + y_offs, kDrawDepth);
if (punch_held_) {
c.Scale(stretch * b_width, squash * b_width);
} else {
c.Scale(b_width, b_width);
}
c.DrawModel(g_media->GetModel(SystemModelID::kActionButtonLeft));
c.PopTransform();
}
// Jump.
was_held =
jump_held_ || (real_time - last_jump_press_time_ < residual_time);
if ((button_fade_ > 0.0f) || jump_held_ || was_held) {
pop = std::max(0.0f,
1.0f
- static_cast<float>(real_time - last_jump_press_time_)
/ pop_time);
if (was_held) {
c.SetColor(1.8f * pop, 1.2f + 0.9f * pop, 2.0f * pop, 1.0f);
} else {
c.SetColor(0.0f, 0.8f * s_extra, 0.0f, base_fade * button_fade_);
}
c.PushTransform();
c.Translate(buttons_x_ + x_offs,
buttons_y_ - button_spread_s - half_b_width + y_offs,
kDrawDepth);
if (jump_held_) {
c.Scale(squash * b_width, stretch * b_width);
} else {
c.Scale(b_width, b_width);
}
c.DrawModel(g_media->GetModel(SystemModelID::kActionButtonBottom));
c.PopTransform();
}
// Pickup.
was_held =
pickup_held_ || (real_time - last_pickup_press_time_ < residual_time);
if ((button_fade_ > 0.0f) || pickup_held_ || was_held) {
pop = std::max(
0.0f, 1.0f
- static_cast<float>(real_time - last_pickup_press_time_)
/ pop_time);
if (was_held) {
c.SetColor(0.5f + 1.4f * pop, 0.8f + 2.4f * pop, 2.0f + 0.4f * pop,
1.0f);
} else {
c.SetColor(0.3f * s_extra, 0.65f * s_extra, 1.0f * s_extra,
base_fade * button_fade_);
}
c.PushTransform();
c.Translate(buttons_x_ + x_offs,
buttons_y_ + button_spread_s + half_b_width + y_offs,
kDrawDepth);
if (pickup_held_) {
c.Scale(squash * b_width, stretch * b_width);
} else {
c.Scale(b_width, b_width);
}
c.DrawModel(g_media->GetModel(SystemModelID::kActionButtonTop));
c.PopTransform();
}
// Center point.
if (buttons_touch_ && action_control_type_ == ActionControlType::kSwipe) {
c.SetTexture(g_media->GetTexture(SystemTextureID::kCircle));
c.SetColor(1.0f, 1.0f, 0.0f, 0.8f);
c.PushTransform();
// We need to scale this up/down relative to the scale we're drawing at
// since we're not drawing in screen space.
float diff_x = buttons_touch_x_ - buttons_x_;
float diff_y = buttons_touch_y_ - buttons_y_;
if (have_player_position) {
c.Translate(buttons_x_
+ 2.3f * world_draw_scale_ * diff_x
/ (base_controls_scale_ * controls_scale_actions_)
+ x_offs,
buttons_y_
+ 2.3f * world_draw_scale_ * diff_y
/ (base_controls_scale_ * controls_scale_actions_)
+ y_offs,
kDrawDepth);
} else {
c.Translate(buttons_x_ + 0.5f * 1.55f * 2.3f * diff_x + x_offs,
buttons_y_ + 0.5f * 1.55f * 2.3f * diff_y + y_offs,
kDrawDepth);
}
c.Scale(b_width * 0.3f, b_width * 0.3f);
c.DrawModel(g_media->GetModel(SystemModelID::kImage1x1));
c.PopTransform();
}
c.PopTransform();
}
c.Submit();
bool draw_in_world = have_player_position;
// Always draw when we've got a world-pos. if not, only draw on screen in
// swipe mode.
if (d_pad_touch_
&& (draw_in_world
|| movement_control_type_ == MovementControlType::kSwipe)) {
// Circle.
SimpleComponent c2(draw_in_world ? frame_def->overlay_3d_pass()
: frame_def->GetOverlayFlatPass());
c2.SetTransparent(true);
if (buttons_touch_) {
c2.SetColor(1.0f, 0.3f, 0.2f, 0.45f);
} else {
c2.SetColor(1.0f, 1.0f, 0.0f, 0.45f);
}
bool zero_len;
if (std::abs(d_pad_draw_x_) > 0.00001f
|| std::abs(d_pad_draw_y_) > 0.00001f) {
d_pad_draw_dir_ = Vector3f(d_pad_draw_x_, 0.0f, -d_pad_draw_y_);
zero_len = false;
} else {
zero_len = true;
}
// Line.
float dist = sqrtf(d_pad_draw_dir_.x * d_pad_draw_dir_.x
+ d_pad_draw_dir_.z * d_pad_draw_dir_.z);
if (zero_len) {
dist = 0.05f;
}
c2.SetTexture(g_media->GetTexture(SystemTextureID::kArrow));
Matrix44f orient =
Matrix44fOrient(d_pad_draw_dir_, Vector3f(0.0f, 1.0f, 0.0f));
c2.PushTransform();
// Drawing in the 3d world.
if (draw_in_world) {
c2.Translate(player_position[0], player_position[1] - 0.5f,
player_position[2]);
// In happy thoughts mode show the arrow on the xy plane instead of xz.
if (g_graphics->camera()->happy_thoughts_mode()) {
c2.Translate(0.0f, 0.5f, 0.0f);
c2.Rotate(90.0f, 1.0f, 0.0f, 0.0f);
}
} else {
// Drawing on 2d overlay.
float s = base_controls_scale_ * controls_scale_move_;
c2.Translate(d_pad_start_x_ + s * 50.0f, d_pad_start_y_ + s * 50.0f,
0.0f);
c2.ScaleUniform(s * 50.0f);
c2.Rotate(90.0f, 1.0f, 0.0f, 0.0f);
}
c2.MultMatrix(orient.m);
c2.Rotate(-90.0f, 1.0f, 0.0f, 0.0f);
c2.ScaleUniform(0.8f);
c2.PushTransform();
c2.Translate(0.0f, dist * -0.5f, 0.0f);
c2.Scale(0.15f, dist, 0.2f);
c2.DrawModel(g_media->GetModel(SystemModelID::kArrowBack));
c2.PopTransform();
c2.PushTransform();
c2.Translate(0.0f, dist * -1.0f - 0.15f, 0.0f);
c2.Scale(0.45f, 0.3f, 0.3f);
c2.DrawModel(g_media->GetModel(SystemModelID::kArrowFront));
c2.PopTransform();
c2.PopTransform();
c2.Submit();
}
}
void TouchInput::UpdateMapping() {
assert(InGameThread());
std::string touch_movement_type =
g_app_config->Resolve(AppConfig::StringID::kTouchMovementControlType);
if (touch_movement_type == "swipe") {
movement_control_type_ = TouchInput::MovementControlType::kSwipe;
} else if (touch_movement_type == "joystick") {
movement_control_type_ = TouchInput::MovementControlType::kJoystick;
} else {
Log("Error: Invalid touch-movement-type: " + touch_movement_type);
movement_control_type_ = TouchInput::MovementControlType::kSwipe;
}
std::string touch_action_type =
g_app_config->Resolve(AppConfig::StringID::kTouchActionControlType);
if (touch_action_type == "swipe") {
action_control_type_ = TouchInput::ActionControlType::kSwipe;
} else if (touch_action_type == "buttons") {
action_control_type_ = TouchInput::ActionControlType::kButtons;
} else {
Log("Error: Invalid touch-action-type: " + touch_action_type);
action_control_type_ = TouchInput::ActionControlType::kSwipe;
}
controls_scale_move_ =
g_app_config->Resolve(AppConfig::FloatID::kTouchControlsScaleMovement);
controls_scale_actions_ =
g_app_config->Resolve(AppConfig::FloatID::kTouchControlsScaleActions);
swipe_controls_hidden_ =
g_app_config->Resolve(AppConfig::BoolID::kTouchControlsSwipeHidden);
// Start with defaults.
switch (GetUIScale()) {
case UIScale::kSmall:
buttons_default_frac_x_ = 0.88f;
buttons_default_frac_y_ = 0.2f;
d_pad_default_frac_x_ = 0.12f;
d_pad_default_frac_y_ = 0.2f;
break;
case UIScale::kMedium:
buttons_default_frac_x_ = 0.89f;
buttons_default_frac_y_ = 0.2f;
d_pad_default_frac_x_ = 0.11f;
d_pad_default_frac_y_ = 0.2f;
break;
default:
buttons_default_frac_x_ = 0.9f;
buttons_default_frac_y_ = 0.3f;
d_pad_default_frac_x_ = 0.1f;
d_pad_default_frac_y_ = 0.3f;
break;
}
// Now override with config.
d_pad_default_frac_x_ =
g_python->GetRawConfigValue("Touch DPad X", d_pad_default_frac_x_);
d_pad_default_frac_y_ =
g_python->GetRawConfigValue("Touch DPad Y", d_pad_default_frac_y_);
buttons_default_frac_x_ =
g_python->GetRawConfigValue("Touch Buttons X", buttons_default_frac_x_);
buttons_default_frac_y_ =
g_python->GetRawConfigValue("Touch Buttons Y", buttons_default_frac_y_);
}
auto TouchInput::HandleTouchDown(void* touch, float x, float y) -> bool {
assert(InGameThread());
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
// If we're in edit mode, see if the touch should become an edit-dpad touch or
// an edit-button touch.
if (editing_) {
float x_diff = x - d_pad_base_x_;
float y_diff = y - d_pad_base_y_;
float len = sqrtf(x_diff * x_diff + y_diff * y_diff)
/ (base_controls_scale_ * controls_scale_move_);
if (len < 40.0f) {
d_pad_drag_touch_ = touch;
d_pad_drag_x_offs_ = x_diff;
d_pad_drag_y_offs_ = y_diff;
return true;
}
x_diff = x - buttons_x_;
y_diff = y - buttons_y_;
len = sqrtf(x_diff * x_diff + y_diff * y_diff)
/ (base_controls_scale_ * controls_scale_actions_);
if (len < 40.0f) {
buttons_drag_touch_ = touch;
buttons_drag_x_offs_ = x_diff;
buttons_drag_y_offs_ = y_diff;
return true;
}
return false; // We don't claim the event.
} else {
// Normal in-game operation:
// Normal operation is disabled while a UI is up.
if (g_ui->IsWindowPresent()) {
return false;
}
if (!attached_to_player()) {
// Ignore touches at the very top (so we don't interfere with the menu).
if (y < height * 0.8f) {
RequestPlayer();
// Joining with the touchscreen can sometimes
// be accidental if there's a trackpad on the controller.
// ..so lets issue a warning to that effect if there's already
// controllers active.. (only if we got a player though).
if (attached_to_player() && g_input->HaveControllerWithPlayer()) {
ScreenMessage(g_game->GetResourceString("touchScreenJoinWarningText"),
{1.0f, 1.0f, 0.0f});
}
}
} else {
// If its on the left side, this is our new dpad touch.
if (x < width * 0.5f) {
d_pad_touch_ = touch;
did_first_move_ = false;
if (movement_control_type_ == MovementControlType::kSwipe) {
d_pad_base_x_ = x;
d_pad_base_y_ = y;
}
d_pad_x_ = x;
d_pad_y_ = y;
d_pad_start_x_ = x;
d_pad_start_y_ = y;
UpdateDPad();
} else if (y < height * 0.8f) {
// Its on the right side (and below the menu), handle buttons.
// Start running if this is a new press.
if (buttons_touch_ == nullptr) {
InputCommand(InputType::kRun, 1.0f);
// in swipe mode we count this as a fly-press
if (action_control_type_ == ActionControlType::kSwipe) {
InputCommand(InputType::kFlyPress);
}
}
buttons_touch_ = touch;
buttons_touch_x_ = buttons_touch_start_x_ = x;
buttons_touch_y_ = buttons_touch_start_y_ = y;
UpdateButtons(true);
}
}
}
return true;
}
auto TouchInput::HandleTouchUp(void* touch, float x, float y) -> bool {
assert(InGameThread());
// Release dpad drag touch.
if (touch == d_pad_drag_touch_) {
d_pad_drag_touch_ = nullptr;
// Write the current frac to our config.
g_python->SetRawConfigValue("Touch DPad X", d_pad_default_frac_x_);
g_python->SetRawConfigValue("Touch DPad Y", d_pad_default_frac_y_);
}
if (touch == buttons_drag_touch_) {
buttons_drag_touch_ = nullptr;
// Write the current frac to our config.
g_python->SetRawConfigValue("Touch Buttons X", buttons_default_frac_x_);
g_python->SetRawConfigValue("Touch Buttons Y", buttons_default_frac_y_);
}
// Release on button touch.
if (touch == buttons_touch_) {
InputCommand(InputType::kRun, 0.0f);
if (action_control_type_ == ActionControlType::kSwipe) {
InputCommand(InputType::kFlyRelease);
}
buttons_touch_x_ = x;
buttons_touch_y_ = y;
buttons_touch_ = nullptr;
UpdateButtons();
}
// If it was our dpad touch, stop tracking.
if (touch == d_pad_touch_) {
d_pad_x_ = d_pad_base_x_;
d_pad_y_ = d_pad_base_y_;
d_pad_touch_ = nullptr;
UpdateDPad();
}
return true;
}
auto TouchInput::HandleTouchMoved(void* touch, float x, float y) -> bool {
assert(InGameThread());
if (touch == d_pad_drag_touch_) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
float ratio_x =
std::min(0.45f, std::max(0.0f, (x - d_pad_drag_x_offs_) / width));
float ratio_y =
std::min(0.9f, std::max(0.0f, (y - d_pad_drag_y_offs_) / height));
d_pad_default_frac_x_ = ratio_x;
d_pad_default_frac_y_ = ratio_y;
}
if (touch == buttons_drag_touch_) {
float width = g_graphics->screen_virtual_width();
float height = g_graphics->screen_virtual_height();
float ratio_x =
std::min(1.0f, std::max(0.55f, (x - buttons_drag_x_offs_) / width));
float ratio_y =
std::min(0.9f, std::max(0.0f, (y - buttons_drag_y_offs_) / height));
buttons_default_frac_x_ = ratio_x;
buttons_default_frac_y_ = ratio_y;
}
// Ignore button/pad touches while gui is up.
if (g_ui->IsWindowPresent()) {
return false;
}
if (touch == buttons_touch_) {
buttons_touch_x_ = x;
buttons_touch_y_ = y;
UpdateButtons();
}
// If it was our dpad touch, update tracking.
if (touch == d_pad_touch_) {
d_pad_x_ = x;
d_pad_y_ = y;
UpdateDPad();
}
return true;
}
auto TouchInput::GetRawDeviceName() -> std::string { return "TouchScreen"; }
} // namespace ballistica