The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package SDL::Tutorial::3DWorld::Actor::GridSelect;

use 5.008;
use strict;
use warnings;
use SDL::Tutorial::3DWorld         ();
use SDL::Tutorial::3DWorld::OpenGL ();
use SDL::Tutorial::3DWorld::Actor  ();
use OpenGL::List                   ();

# Use proper POSIX math rather than playing games with Perl's int()
use POSIX ();

our $VERSION = '0.33';
our @ISA     = 'SDL::Tutorial::3DWorld::Actor';

sub new {
	my $self = shift->SUPER::new(@_);

	# Gridcubes need blending
	$self->{blending} = 1;

	# The select box starts 2.5 metres in front of the camera
	$self->{distance} = 5;

	# The select box is a 1 metre cube
	# $self->{box} = [ -0.05, -0.05, -0.05, 1.05, 1.05, 1.05 ];

	# We must have some notional velocity to be considered for movement.
	# This is an artifact of the movement optimisation and a bit of a
	# cludge really.
	$self->{velocity} = [ 0, 0, 0 ];

	return $self;
}





######################################################################
# Engine Interface

sub init {
	my $self = shift;
	$self->SUPER::init(@_);

	# Compile the point and cube lists
	$self->{list} = OpenGL::List::glpList {
		$self->compile;
	};

	return 1;
}

sub move {
	my $self   = shift;
	my $camera = SDL::Tutorial::3DWorld->current->camera;

	# Project the location of the notional point out of the camera
	# to find the camera-relative position of the box.
	my $distance  = $self->{distance};
	my $direction = $camera->{direction};
	my @position  = (
		$distance * $direction->[0],
		$distance * $direction->[1],
		$distance * $direction->[2],
	);

	# Limit the selector position to one unit below the floor
	# for a minecraft-inspired selection effect.
	# When applying the limitation we want to constrain on all
	# three dimensions. This has the effect of making the selected
	# grid position still in front of the camera, which looks more
	# natural than simply moving the selection grid up on the Y axis.
	# Set the Y to a slightly larger value rather than multiplying by
	# the ratio to prevent any floating point issues when we do the
	# POSIX::ceil grid-snapping call later.
	my $lowest_y = 0;
	if ( ($camera->Y + $position[1]) < $lowest_y ) {
		my $have_y = $position[1];
		my $want_y = $lowest_y - $camera->Y;
		my $ratio  = $want_y / $have_y;
		$position[0] *= $ratio;
		$position[1] *= $ratio;
		$position[1] += 0.1;
		$position[2] *= $ratio;
	}

	# Apply the final position to the camera position and snap to the
	# grid on the negative side on all three dimensions, as we will be
	# drawing the grid outwards on the positive side on all three.
	$self->{position} = [
		POSIX::floor( $camera->X + $position[0] ),
		POSIX::floor( $camera->Y + $position[1] ),
		POSIX::floor( $camera->Z + $position[2] ),
	];

	return 1;
}

sub display {
	my $self = shift;

	# Translate to the correct location
	$self->SUPER::display(@_);

	# Draw the 1m cube at the location
	OpenGL::glCallList( $self->{list} );
}

# The compilable section of the grid cube display logic
sub compile {
	# The cube is plain opaque full-bright white and ignores lighting
	OpenGL::glDisable( OpenGL::GL_LIGHTING );
	OpenGL::glDisable( OpenGL::GL_TEXTURE_2D );

	# Enable line smoothing
	OpenGL::glBlendFunc( OpenGL::GL_SRC_ALPHA, OpenGL::GL_ONE_MINUS_SRC_ALPHA );
	OpenGL::glEnable( OpenGL::GL_BLEND );
	OpenGL::glEnable( OpenGL::GL_LINE_SMOOTH );

	# Draw all the lines in the cube
	OpenGL::glLineWidth(1);
	OpenGL::glColor4f( 1.0, 1.0, 1.0, 1.0 );
	OpenGL::glBegin( OpenGL::GL_LINES );
	OpenGL::glVertex3f( -0.001, -0.001, -0.001 ); OpenGL::glVertex3f(  1.001, -0.001, -0.001 );
	OpenGL::glVertex3f( -0.001, -0.001, -0.001 ); OpenGL::glVertex3f( -0.001,  1.001, -0.001 );
	OpenGL::glVertex3f( -0.001, -0.001, -0.001 ); OpenGL::glVertex3f( -0.001, -0.001, 1 );
	OpenGL::glVertex3f(  1.001, -0.001, -0.001 ); OpenGL::glVertex3f(  1.001,  1.001, -0.001 );
	OpenGL::glVertex3f(  1.001, -0.001, -0.001 ); OpenGL::glVertex3f(  1.001, -0.001,  1.001 );
	OpenGL::glVertex3f( -0.001,  1.001, -0.001 ); OpenGL::glVertex3f(  1.001,  1.001, -0.001 );
	OpenGL::glVertex3f( -0.001,  1.001, -0.001 ); OpenGL::glVertex3f( -0.001,  1.001,  1.001 );
	OpenGL::glVertex3f( -0.001, -0.001,  1.001 ); OpenGL::glVertex3f(  1.001, -0.001,  1.001 );
	OpenGL::glVertex3f( -0.001, -0.001,  1.001 ); OpenGL::glVertex3f( -0.001,  1.001,  1.001 );
	OpenGL::glVertex3f(  1.001,  1.001, -0.001 ); OpenGL::glVertex3f(  1.001,  1.001,  1.001 );
	OpenGL::glVertex3f(  1.001, -0.001,  1.001 ); OpenGL::glVertex3f(  1.001,  1.001,  1.001 );
	OpenGL::glVertex3f( -0.001,  1.001,  1.001 ); OpenGL::glVertex3f(  1.001,  1.001,  1.001 );
	OpenGL::glEnd();

	# Disable line smoothing
	OpenGL::glDisable( OpenGL::GL_LINE_SMOOTH );
	OpenGL::glDisable( OpenGL::GL_BLEND );

	# Restore lighting
	OpenGL::glEnable( OpenGL::GL_LIGHTING );
}

1;