package UAV::Pilot::ARDrone::SDLNavOutput;
use v5.14;
use Moose;
use namespace::autoclean;
use File::Spec;
use Math::Trig ();
use SDL;
use SDLx::Text;
use SDL::Event;
use SDL::Events;
use UAV::Pilot;
use UAV::Pilot::ARDrone::NavPacket;
use constant {
BG_COLOR => [ 0, 0, 0 ],
DRAW_VALUE_COLOR => [ 0x33, 0xff, 0x33 ],
DRAW_FEEDER_VALUE_COLOR => [ 0x33, 0x33, 0xff ],
DRAW_CIRCLE_VALUE_COLOR => [ 0xa8, 0xa8, 0xa8 ],
TEXT_LABEL_COLOR => [ 0, 0, 255 ],
TEXT_VALUE_COLOR => [ 255, 0, 0 ],
TEXT_SIZE => 20,
TEXT_FONT => 'typeone.ttf',
ROLL_LABEL_X => 50,
PITCH_LABEL_X => 150,
YAW_LABEL_X => 250,
ALTITUDE_LABEL_X => 350,
BATTERY_LABEL_X => 450,
ROLL_VALUE_X => 50,
PITCH_VALUE_X => 150,
YAW_VALUE_X => 250,
ALTITUDE_VALUE_X => 350,
BATTERY_VALUE_X => 450,
ROLL_DISPLAY_X => 50,
PITCH_DISPLAY_X => 150,
YAW_DISPLAY_X => 250,
ALTITUDE_DISPLAY_X => 350,
VERT_SPEED_DISPLAY_HALF_HEIGHT => 10,
VERT_SPEED_DISPLAY_WIDTH => 10,
VERT_SPEED_BORDER_WIDTH_MARGIN => 2,
BATTERY_DISPLAY_X => 450,
LINE_VALUE_HALF_MAX_HEIGHT => 10,
LINE_VALUE_HALF_LENGTH => 40,
CIRCLE_VALUE_RADIUS => 40,
BAR_MAX_HEIGHT => 40,
BAR_WIDTH => 10,
BAR_PERCENT_COLOR_GRADIENT => [
[ '255', '0', '0' ],
[ '252', '3', '0' ],
[ '250', '5', '0' ],
[ '247', '8', '0' ],
[ '245', '10', '0' ],
[ '242', '13', '0' ],
[ '240', '15', '0' ],
[ '237', '18', '0' ],
[ '234', '21', '0' ],
[ '232', '23', '0' ],
[ '229', '26', '0' ],
[ '227', '28', '0' ],
[ '224', '31', '0' ],
[ '222', '33', '0' ],
[ '219', '36', '0' ],
[ '216', '39', '0' ],
[ '214', '41', '0' ],
[ '211', '44', '0' ],
[ '209', '46', '0' ],
[ '206', '49', '0' ],
[ '203', '52', '0' ],
[ '201', '54', '0' ],
[ '198', '57', '0' ],
[ '196', '59', '0' ],
[ '193', '62', '0' ],
[ '191', '64', '0' ],
[ '188', '67', '0' ],
[ '185', '70', '0' ],
[ '183', '72', '0' ],
[ '180', '75', '0' ],
[ '178', '77', '0' ],
[ '175', '80', '0' ],
[ '173', '82', '0' ],
[ '170', '85', '0' ],
[ '167', '88', '0' ],
[ '165', '90', '0' ],
[ '162', '93', '0' ],
[ '160', '95', '0' ],
[ '157', '98', '0' ],
[ '155', '100', '0' ],
[ '152', '103', '0' ],
[ '149', '106', '0' ],
[ '147', '108', '0' ],
[ '144', '111', '0' ],
[ '142', '113', '0' ],
[ '139', '116', '0' ],
[ '137', '118', '0' ],
[ '134', '121', '0' ],
[ '131', '124', '0' ],
[ '129', '126', '0' ],
[ '126', '129', '0' ],
[ '124', '131', '0' ],
[ '121', '134', '0' ],
[ '118', '137', '0' ],
[ '116', '139', '0' ],
[ '113', '142', '0' ],
[ '111', '144', '0' ],
[ '108', '147', '0' ],
[ '106', '149', '0' ],
[ '103', '152', '0' ],
[ '100', '155', '0' ],
[ '98', '157', '0' ],
[ '95', '160', '0' ],
[ '93', '162', '0' ],
[ '90', '165', '0' ],
[ '88', '167', '0' ],
[ '85', '170', '0' ],
[ '82', '173', '0' ],
[ '80', '175', '0' ],
[ '77', '178', '0' ],
[ '75', '180', '0' ],
[ '72', '183', '0' ],
[ '70', '185', '0' ],
[ '67', '188', '0' ],
[ '64', '191', '0' ],
[ '62', '193', '0' ],
[ '59', '196', '0' ],
[ '57', '198', '0' ],
[ '54', '201', '0' ],
[ '52', '203', '0' ],
[ '49', '206', '0' ],
[ '46', '209', '0' ],
[ '44', '211', '0' ],
[ '41', '214', '0' ],
[ '39', '216', '0' ],
[ '36', '219', '0' ],
[ '33', '222', '0' ],
[ '31', '224', '0' ],
[ '28', '227', '0' ],
[ '26', '229', '0' ],
[ '23', '232', '0' ],
[ '21', '234', '0' ],
[ '18', '237', '0' ],
[ '15', '240', '0' ],
[ '13', '242', '0' ],
[ '10', '245', '0' ],
[ '8', '247', '0' ],
[ '5', '250', '0' ],
[ '3', '252', '0' ],
[ '0', '255', '0' ],
],
};
has 'feeder' => (
is => 'ro',
isa => 'Maybe[UAV::Pilot::SDL::NavFeeder]',
);
has 'width' => (
is => 'ro',
isa => 'Int',
default => 640,
);
has 'height' => (
is => 'ro',
isa => 'Int',
default => 200,
);
has '_txt_label' => (
is => 'ro',
isa => 'SDLx::Text',
);
has '_txt_value' => (
is => 'ro',
isa => 'SDLx::Text',
);
has '_last_nav_packet' => (
is => 'ro',
isa => 'Maybe[UAV::Pilot::ARDrone::NavPacket]',
writer => 'got_new_nav_packet',
);
with 'UAV::Pilot::SDL::WindowEventHandler';
with 'UAV::Pilot::NavCollector';
with 'UAV::Pilot::Logger';
sub BUILDARGS
{
my ($class, $args) = @_;
my @txt_color_parts = @{ $class->TEXT_LABEL_COLOR };
my @txt_value_color_parts = @{ $class->TEXT_VALUE_COLOR };
my $font_path = File::Spec->catfile(
UAV::Pilot->default_module_dir,
$class->TEXT_FONT,
);
my $label = SDLx::Text->new(
font => $font_path,
color => [ @txt_color_parts ],
size => $class->TEXT_SIZE,
h_align => 'center',
);
my $value = SDLx::Text->new(
font => $font_path,
color => [ @txt_value_color_parts ],
size => $class->TEXT_SIZE,
h_align => 'center',
);
$$args{_txt_label} = $label;
$$args{_txt_value} = $value;
return $args;
}
sub draw
{
my ($self, $window) = @_;
my $nav = $self->_last_nav_packet;
if(! defined $nav) {
$self->_logger->info( 'No nav packet yet, not drawing anything' );
return $self->_draw_no_nav_packet( $window );
}
$self->_logger->info( 'Drawing nav packet' );
$window->clear_screen;
my $txt_label = $self->_txt_label;
$window->draw_txt( 'ROLL', $self->ROLL_LABEL_X, 150, $txt_label);
$window->draw_txt( 'PITCH', $self->PITCH_LABEL_X, 150, $txt_label);
$window->draw_txt( 'YAW', $self->YAW_LABEL_X, 150, $txt_label);
$window->draw_txt( 'ALTITUDE', $self->ALTITUDE_LABEL_X, 150, $txt_label);
$window->draw_txt( 'BATTERY', $self->BATTERY_LABEL_X, 150, $txt_label);
my $txt_val = $self->_txt_value;
$window->draw_txt( sprintf('%.2f', $nav->roll ),
$self->ROLL_VALUE_X, 30, $txt_val );
$window->draw_txt( sprintf('%.2f', $nav->pitch ),
$self->PITCH_VALUE_X, 30, $txt_val );
$window->draw_txt( sprintf('%.2f', $nav->yaw ),
$self->YAW_VALUE_X, 30, $txt_val );
$window->draw_txt( sprintf('%.2f cm', $nav->altitude ),
$self->ALTITUDE_VALUE_X, 30, $txt_val );
$window->draw_txt( $nav->battery_voltage_percentage . '%',
$self->BATTERY_VALUE_X, 30, $txt_val );
my $line_color = $self->DRAW_VALUE_COLOR;
my $feeder = $self->feeder;
if( defined $feeder) {
my $feeder_line_color = $self->DRAW_FEEDER_VALUE_COLOR;
$self->_draw_line_value( $feeder->cur_roll,
$self->ROLL_DISPLAY_X, 100,
$feeder_line_color, $window );
$self->_draw_line_value( $feeder->cur_pitch,
$self->PITCH_DISPLAY_X, 100,
$feeder_line_color, $window );
$self->_draw_circle_value( $feeder->cur_yaw,
$self->YAW_DISPLAY_X, 100,
$feeder_line_color, $window );
$self->_draw_line_vert_indicator( $feeder->cur_vert_speed,
$self->ALTITUDE_VALUE_X, 100, $self->VERT_SPEED_DISPLAY_HALF_HEIGHT,
$self->VERT_SPEED_DISPLAY_WIDTH, $feeder_line_color, $line_color,
$self->VERT_SPEED_BORDER_WIDTH_MARGIN,
$window );
}
$self->_draw_line_value( $nav->roll, $self->ROLL_DISPLAY_X, 100, $line_color, $window );
$self->_draw_line_value( $nav->pitch, $self->PITCH_DISPLAY_X, 100, $line_color, $window );
$self->_draw_circle_value( $nav->yaw, $self->YAW_DISPLAY_X, 100, $line_color, $window );
# Should we draw anything for altitude?
$self->_draw_bar_percent_value( $nav->battery_voltage_percentage,
$self->BATTERY_DISPLAY_X, 100, $window );
$self->_logger->info( 'Done drawing nav packet' );
return 1;
}
before 'got_new_nav_packet' => sub {
my ($self, $packet) = @_;
$self->_logger->info( 'Received nav packet to draw' );
return 1;
};
sub _draw_line_value
{
my ($self, $value, $center_x, $center_y, $color, $window) = @_;
my $y_addition = int( $self->LINE_VALUE_HALF_MAX_HEIGHT * $value );
my $right_y = $center_y - $y_addition;
my $left_y = $center_y + $y_addition;
my $right_x = $center_x + $self->LINE_VALUE_HALF_LENGTH;
my $left_x = $center_x - $self->LINE_VALUE_HALF_LENGTH;
$window->draw_line( [$left_x, $left_y], [$right_x, $right_y], $color );
return 1;
}
sub _draw_circle_value
{
my ($self, $value, $center_x, $center_y, $value_color, $window) = @_;
my $radius = $self->CIRCLE_VALUE_RADIUS;
my $color = $self->DRAW_VALUE_COLOR;
my $angle = Math::Trig::pip2 * $value; # Note use of radians, not degrees
my $line_x = $center_x - (sin($angle) * $radius);
my $line_y = $center_y - (cos($angle) * $radius);
$window->draw_circle( [$center_x, $center_y], $radius, $color );
$window->draw_line( [$center_x, $center_y],
[$center_x, $center_y - $radius], $color );
$window->draw_line( [$center_x, $center_y], [$line_x, $line_y],
$value_color );
return 1;
}
sub _draw_bar_percent_value
{
my ($self, $value, $center_x, $center_y, $window) = @_;
my $color = $self->BAR_PERCENT_COLOR_GRADIENT->[$value - 1];
my $half_max_height = $self->BAR_MAX_HEIGHT / 2;
my $half_width = $self->BAR_WIDTH / 2;
my $left_x = $center_x - $half_width;
my $right_x = $center_x + $half_width;
my $top_y = $center_y - $half_max_height;
my $bottom_y = $center_y + $half_max_height;
my $percentage_height = sprintf( '%.0f', $self->BAR_MAX_HEIGHT * ($value / 100) );
my $top_percentage_y = $bottom_y - $percentage_height;
$window->draw_line( $$_[0], $$_[1], $color ) for (
[ [$left_x, $top_y], [$right_x, $top_y] ],
[ [$right_x, $top_y], [$right_x, $bottom_y] ],
[ [$right_x, $bottom_y], [$left_x, $bottom_y] ],
[ [$left_x, $bottom_y], [$left_x, $top_y] ],
);
$window->draw_rect( [ $left_x, $top_percentage_y,
$self->BAR_WIDTH, $percentage_height ], $color );
return 1;
}
sub _draw_line_vert_indicator
{
my ($self, $value, $center_x, $center_y, $half_height, $width, $color, $top_bottom_color, $border_width_margin, $window) = @_;
my $half_width = $width / 2;
my $left_x = $center_x - $half_width;
my $right_x = $center_x + $half_width;
my $border_left_x = $left_x - $border_width_margin;
my $border_right_x = $right_x + $border_width_margin;
my $border_top_y = $center_y - $half_height;
my $border_bottom_y = $center_y + $half_height;
my $indicator_y = $center_y - ($half_height * $value);
$window->draw_line( [$border_left_x, $border_top_y],
[$border_right_x, $border_top_y], $top_bottom_color );
$window->draw_line( [$border_left_x, $border_bottom_y],
[$border_right_x, $border_bottom_y], $top_bottom_color );
$window->draw_line( [$left_x, $indicator_y], [$right_x, $indicator_y],
$color );
return 1;
}
sub _draw_no_nav_packet
{
my ($self, $window) = @_;
$window->clear_screen;
my $txt_label = $self->_txt_label;
$window->draw_txt( 'No Nav Data Received', 20, 100, $txt_label );
return 1;
}
no Moose;
__PACKAGE__->meta->make_immutable;
1;
__END__
=head1 NAME
UAV::Pilot::ARDrone::SDLNavOutput
=head1 SYNOPSIS
my $condvar = AnyEvent->condvar;
my $events = UAV::Pilot::Events->new({
condvar => $condvar,
});
my $window = UAV::Pilot::SDL::Window->new;
my $sdl_nav = UAV::Pilot::ARDrone::SDLNavOutput->new({
driver => UAV::Pilot::ARDrone::Driver->new( ... ),
window => $window,
});
$events->register( $sdl_nav );
=head1 DESCRIPTION
Graphically renders a C<UAV::Pilot::ARDrone::NavPacket> using SDL.
It does the C<UAV::Pilot::EventHandler> role, and thus can be processed by
C<UAV::Pilot::Events>. It's recommended to also add the
C<UAV::Pilot::SDL::Events> handler to the events object, as that will
take care of the C<SDL_QUIT> events. Without that, there's no way to stop
the process other than C<kill -9>.
=head1 METHODS
=head2 new
new({
feeder => ...
})
Constructor. The param C<feeder> takes a C<UAV::Pilot::SDL::NavFeeder> object.
=head2 render
render( $nav_packet )
Updates the graphic with the given nav packet data.
=cut