The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
# This is OGRE's "Intermediate Tutorial 3" but in Perl
# and using BetaGUI instead of CEGUI.


package MouseQueryGUI;

use strict;
use warnings;

use Ogre::BetaGUI::BetaGUIListener;
@MouseQueryGUI::ISA = qw(Ogre::BetaGUI::BetaGUIListener);

use Ogre 0.37;
use Ogre::BetaGUI::GUI;

sub new {
    my ($pkg) = @_;

    my $super = $pkg->SUPER::new();
    my $self = bless $super, $pkg;

    $self->{mouseX} = 0;
    $self->{mouseY} = 0;

    $self->{mBetaGUI} = Ogre::BetaGUI::GUI->new("WhoCares", "BlueHighway", 20);
    # note: the C++ version uses Vector2, but we just use array refs
    $self->{mPointer} = $self->{mBetaGUI}->createMousePointer([32,32], "bgui.pointer");

    return $self;
}

sub onButtonPress {
    my ($self, $button, $lmb) = @_;

    # empty...
}


1;


package MouseQueryListener;

use strict;
use warnings;

use Ogre::ExampleFrameListener;
@MouseQueryListener::ISA = qw(Ogre::ExampleFrameListener);

use Ogre 0.34;
use Ogre::Ray;
use Ogre::Vector3;

use OIS 0.04;
use OIS::Mouse;

use constant ROBOT_MASK => 1;
use constant NINJA_MASK => 2;


sub new {
    my ($pkg, $win, $cam, $sceneManager, $gui) = @_;

    my $super = $pkg->SUPER::new($win, $cam, 0, 1);
    my $self = bless $super, $pkg;

    $self->{mMQGUI} = $gui;

    $self->{mCount} = 0;
    $self->{mCurrentObject} = undef;
    $self->{mLMouseDown} = 0;
    $self->{mRMouseDown} = 0;
    $self->{mSceneMgr} = $sceneManager;

    $self->{mDOWN} = Ogre::Vector3->new(0, -1, 0);

    # Reduce move speed
    $self->{mMoveSpeed} = 50;
    $self->{mRotateSpeed} /= 500;

    # Register this so that we get mouse events.
    $self->{mMouse}->setEventCallback($self);

    # Create RaySceneQuery
    $self->{mRaySceneQuery} = $self->{mSceneMgr}->createRayQuery(Ogre::Ray->new());

    $self->{mRobotMode} = 1;
    $self->{mDebugText} = "Robot Mode Enabled - Press Space to Toggle";

    return $self;
}

sub frameStarted {
    my ($self, $evt) = @_;

    return 0 unless $self->SUPER::frameStarted($evt);

    # swap modes
    if ($self->{mKeyboard}->isKeyDown(OIS::Keyboard->KC_SPACE) && $self->{mTimeUntilNextToggle} <= 0)
    {
        $self->{mRobotMode} = ! $self->{mRobotMode};
        $self->{mTimeUntilNextToggle} = 1;
        $self->{mDebugText} = ($self->{mRobotMode} ? "Robot" : "Ninja") . " Mode Enabled - Press Space to Toggle";
    }

    # Setup the scene query
    my $camPos = $self->{mCamera}->getPosition();
    my $cameraRay = Ogre::Ray->new(Ogre::Vector3->new($camPos->x, 5000.0, $camPos->z),
                                   $self->{mDOWN});
    $self->{mRaySceneQuery}->setRay($cameraRay);
    $self->{mRaySceneQuery}->setSortByDistance(0);

    # Perform the scene query
    my $result = $self->{mRaySceneQuery}->execute();

    # Get the results, set the camera height
    foreach my $itr (@$result) {
        if (defined $itr->{worldFragment}) {
            my $terrainHeight = $itr->{worldFragment}->singleIntersection->y;
            if (($terrainHeight + 10.0) > $camPos->y) {
                $self->{mCamera}->setPosition($camPos->x, $terrainHeight + 10.0, $camPos->z);
            }
            last;
        }
    }

    return 1;
}

# MouseListener callbacks
sub mouseReleased {
    my ($self, $arg, $id) = @_;

    # Left mouse button up
    if ($id == OIS::Mouse->MB_Left) {
        # this doesn't do anything, so why call it....
        # $self->onLeftReleased($arg);
        $self->{mLMouseDown} = 0;
    }

    # Right mouse button up
    elsif ($id == OIS::Mouse->MB_Right) {
        $self->onRightReleased($arg);
        $self->{mRMouseDown} = 0;
    }

    return 1;
}

sub mousePressed {
    my ($self, $arg, $id) = @_;

    # Left mouse button down
    if ($id == OIS::Mouse->MB_Left) {
        $self->onLeftPressed($arg);
        $self->{mLMouseDown} = 1;
    }

    # Right mouse button down
    elsif ($id == OIS::Mouse->MB_Right) {
        $self->onRightPressed($arg);
        $self->{mRMouseDown} = 1;
    }

    return 1;
}

sub mouseMoved {
    my ($self, $arg) = @_;
    my $state = $arg->state;

    # Update BetaGUI with the mouse motion
    $self->injectMouse($state);

    # If we are dragging the left mouse button.
    if ($self->{mLMouseDown}) {
        my $mouseRay = $self->{mCamera}->getCameraToViewportRay($self->{mMQGUI}{mouseX} / $state->width,
                                                                $self->{mMQGUI}{mouseY} / $state->height);
        $self->{mRaySceneQuery}->setRay($mouseRay);
        $self->{mRaySceneQuery}->setSortByDistance(0);

        my $result = $self->{mRaySceneQuery}->execute();
        foreach my $itr (@$result) {
            if (defined $itr->{worldFragment}) {
                $self->{mCurrentObject}->setPosition($itr->{worldFragment}->singleIntersection);
                last;
            }
        }
    }

    # If we are dragging the right mouse button.
    elsif ($self->{mRMouseDown}) {
        $self->{mCamera}->yaw(Ogre::Degree->new(- $state->X->rel * $self->{mRotateSpeed}));
        $self->{mCamera}->pitch(Ogre::Degree->new(- $state->Y->rel * $self->{mRotateSpeed}));
    }

    return 1;
}

sub onLeftPressed {
    my ($self, $arg) = @_;

    # Turn off bounding box.
    $self->{mCurrentObject}->showBoundingBox(0) if defined $self->{mCurrentObject};

    # Setup the ray scene query
    my $state = $arg->state;
    my $mouseRay = $self->{mCamera}->getCameraToViewportRay($self->{mMQGUI}{mouseX} / $state->width,
                                                            $self->{mMQGUI}{mouseY} / $state->height);
    $self->{mRaySceneQuery}->setRay($mouseRay);
    $self->{mRaySceneQuery}->setSortByDistance(1);
    $self->{mRaySceneQuery}->setQueryMask($self->{mRobotMode} ? ROBOT_MASK : NINJA_MASK);

    # Execute query
    my $result = $self->{mRaySceneQuery}->execute();
    foreach my $itr (@$result) {
        if (defined($itr->{movable}) && $itr->{movable}->getName !~ /^tile\[/) {
            $self->{mCurrentObject} = $itr->{movable}->getParentSceneNode;
            last;
        }

        elsif (defined $itr->{worldFragment}) {
            my ($ent, $name);

            if ($self->{mRobotMode}) {
                $name = sprintf("Robot%d", $self->{mCount}++);
                $ent = $self->{mSceneMgr}->createEntity($name, "robot.mesh");
                $ent->setQueryFlags(ROBOT_MASK);
            }
            else {
                $name = sprintf("Ninja%d", $self->{mCount}++);
                $ent = $self->{mSceneMgr}->createEntity($name, "ninja.mesh");
                $ent->setQueryFlags(NINJA_MASK);
            }

            $self->{mCurrentObject} =
              $self->{mSceneMgr}->getRootSceneNode->createChildSceneNode($name . "Node",
                                                                         $itr->{worldFragment}->singleIntersection);
            $self->{mCurrentObject}->attachObject($ent);
            $self->{mCurrentObject}->setScale(0.1, 0.1, 0.1);
            last;
        }
    }

    # Show the bounding box to highlight the selected object
    $self->{mCurrentObject}->showBoundingBox(1) if defined $self->{mCurrentObject};
}

sub onRightPressed {
    my ($self, $arg) = @_;
    $self->{mMQGUI}{mPointer}->hide();
}

sub onRightReleased {
    my ($self, $arg) = @_;
    $self->{mMQGUI}{mPointer}->show();
}

sub injectMouse {
    my ($self, $state) = @_;

    $self->{mMQGUI}{mouseX} += $state->X->rel;
    $self->{mMQGUI}{mouseY} += $state->Y->rel;
    if ($state->buttons == 1) {
        $self->{mMQGUI}{mBetaGUI}->injectMouse($self->{mMQGUI}{mouseX},
                                               $self->{mMQGUI}{mouseY},
                                               1);  # LMB is down.
    }
    else {
        $self->{mMQGUI}{mBetaGUI}->injectMouse($self->{mMQGUI}{mouseX},
                                               $self->{mMQGUI}{mouseY},
                                               0); # LMB is not down.
    }
}


1;


package MouseQueryApplication;

use strict;
use warnings;

use Ogre::ExampleApplication;
@MouseQueryApplication::ISA = qw(Ogre::ExampleApplication);

use Ogre 0.34 qw(:SceneType);
use Ogre::ColourValue;
use Ogre::Degree;

sub new {
    my ($pkg) = @_;

    my $super = $pkg->SUPER::new();
    my $self = bless $super, $pkg;

    $self->{mMQGUI} = undef;

    return $self;
}

sub chooseSceneManager {
    my ($self) = @_;
    $self->{mSceneMgr} = $self->{mRoot}->createSceneManager(ST_EXTERIOR_CLOSE);
}

sub createScene {
    my ($self) = @_;

    my $scenemgr = $self->{mSceneMgr};

    # ambient light
    $scenemgr->setAmbientLight(Ogre::ColourValue->new(0.5, 0.5, 0.5));
    $scenemgr->setSkyDome(1, "Examples/CloudySky", 5, 8);

    # terrain
    $scenemgr->setWorldGeometry($self->{mResourcePath} . "terrain.cfg");

    # set cam look point
    my $cam = $self->{mCamera};
    $cam->setPosition(40, 100, 580);
    $cam->pitch(Ogre::Degree->new(-30));
    $cam->yaw(Ogre::Degree->new(-45));

    $self->{mMQGUI} = MouseQueryGUI->new();
}

sub createFrameListener {
    my ($self) = @_;

    $self->{mFrameListener} = MouseQueryListener->new($self->{mWindow},
                                                      $self->{mCamera},
                                                      $self->{mSceneMgr},
                                                      $self->{mMQGUI});
    $self->{mFrameListener}->showDebugOverlay(1);
    $self->{mRoot}->addFrameListener($self->{mFrameListener});
}


1;


package main;

# uncomment this if the packages are in separate files:
# use MouseQueryApplication;
MouseQueryApplication->new->go();