#!/usr/bin/perl
# This is OgreAL's "Directional" demo ported to Perl.
# Read README.txt in this directory for how to set
# up the resources for this demo.
#
# A lot of the code here is duplicated from basic.pl.
#
# See Demos/Directional/ in the OgreAL distribution.


package DeviceListener;
# implements FrameListener, WindowEventListener, MouseListener, KeyListener

use strict;
use warnings;

use Ogre 0.39;
use Ogre::Degree;
use Ogre::Node qw(:TransformSpace);
use Ogre::Vector3;
use Ogre::WindowEventUtilities;

use Ogre::AL;
use Ogre::AL::SoundManager;

use OIS 0.05;
use OIS::InputManager;
use OIS::Keyboard qw(:KeyCode);
use OIS::Mouse qw(:MouseButtonID);

use constant ROTATION_SPEED => 10;
use constant MOVEMENT_SPEED => 500;


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

    my $self = bless {
        mSceneMgr => $sceneMgr,
        camera => $cam,
        mWindow => $win,
        mPitchNode => $cam->getParentSceneNode,
        mHornNode => $sceneMgr->getSceneNode("HornNode"),
        mContinue => 1,
        mDirection => Ogre::Vector3->new(0, 0, 0),
        yaw => 0,
        pitch => 0,
        soundAlarm => 0,
        mMouse => undef,
        mKeyboard => undef,
        mInputManager => undef,
        mNumScreenShots => 0,
    }, $pkg;

    $self->{mCamNode} = $self->{mPitchNode}->getParentSceneNode;

    # Initialize OIS (Perl version differs somewhat from C++)
    my $windowHnd = $win->getCustomAttributePtr('WINDOW');
    $self->{mInputManager} = OIS::InputManager->createInputSystemPtr($windowHnd);

    if ($self->{mInputManager}->numMice() > 0) {
        # note again this is a little different than in C++
        $self->{mMouse} = $self->{mInputManager}->createInputObjectMouse(1);
        $self->{mMouse}->setEventCallback($self);
    }

    # some checks just in case you had OIS already installed and it wasn't really version 1.0.0
    my $numkbs = $self->{mInputManager}->can('numKeyboards');
    $numkbs = $self->{mInputManager}->can('numKeyBoards') if not defined $numkbs;
    if ($self->{mInputManager}->$numkbs > 0) {
        # note again this is a little different than in C++
        $self->{mKeyboard} = $self->{mInputManager}->createInputObjectKeyboard(1);
        $self->{mKeyboard}->setEventCallback($self);
    }

    $self->windowResized($win);
    Ogre::WindowEventUtilities->addWindowEventListener($win, $self);

    return $self;
}

# note: until I figure out how to get DESTROY to work properly
# (see Ogre::ExampleFrameListener), whenever you close an app it will
# disable autorepeat. If you have an XWindow system (e.g. Linux),
# you can turn it back on with for example:  xset r rate 300 30
# Thanks to akem on #ogre3d for pointing this out.

# These are the same as in Ogre::ExampleFrameListener
sub windowResized {
    my ($self, $win) = @_;

    my ($width, $height) = $win->getMetrics();
    my $mousestate = $self->{mMouse}->getMouseState();

    # note: in C++ this would be like  mousestate.width = width;
    $mousestate->setWidth($width);
    $mousestate->setHeight($height);
}
sub windowClosed {
    my ($self, $win) = @_;

    # note: NEED TO IMPLEMENT overload == operator (etc...)
    # if ($win == $self->{mWindow}) {
    if ($win->getName == $self->{mWindow}->getName) {
        if ($self->{mInputManager}) {
            my $im = $self->{mInputManager};
            if ($self->{mMouse}) {
                $im->destroyInputObject($self->{mMouse});
                delete $self->{mMouse};
            }
            if ($self->{mKeyboard}) {
                $im->destroyInputObject($self->{mKeyboard});
                delete $self->{mMouse};
            }

            OIS::InputManager->destroyInputSystem($im);
            delete $self->{mInputManager};
        }
    }
}

sub frameStarted {
    my ($self, $evt) = @_;
    my $mouse = $self->{mMouse};
    my $keyboard = $self->{mKeyboard};

    return 0 if $self->{mWindow}->isClosed;

    $keyboard->capture();
    $mouse->capture();

    # rotate camera
    $self->{mCamNode}->yaw($self->{yaw} * $evt->timeSinceLastFrame);
    $self->{mCamNode}->pitch($self->{pitch} * $evt->timeSinceLastFrame);

    $self->{yaw} = 0;
    $self->{pitch} = 0;

    if ($self->{soundAlarm}) {
        $self->{mHornNode}->yaw(Ogre::Degree->new(115 * $evt->timeSinceLastFrame), TS_WORLD);
    }

    # move camera
    # Quaternion * Vector3 * Real   =  fancy!
    $self->{mCamNode}->translate($self->{mPitchNode}->_getDerivedOrientation * $self->{mDirection} * $evt->timeSinceLastFrame);

    # update stats
    my $om = Ogre::OverlayManager->getSingletonPtr();
    my $taname = $om->getOverlayElement("TextAreaName");
    $taname->setCaption($self->{mWindow}->getAverageFPS
                          . "\n\nF1 = Activate Horn");

    return $self->{mContinue};
}

sub mouseMoved {
    my ($self, $arg) = @_;
    my $ms = $self->{mMouse}->getMouseState();

    $self->{yaw} = - Ogre::Degree->new($ms->X->rel * ROTATION_SPEED);
    $self->{pitch} = - Ogre::Degree->new($ms->Y->rel * ROTATION_SPEED);

    return 1;
}

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

    # xxx: I still haven't fixed these OIS constants....
    my $key = $arg->key;
    if ($key == OIS::Keyboard->KC_ESCAPE) {
        $self->{mContinue} = 0;
    }
    elsif ($key == OIS::Keyboard->KC_SYSRQ) {
        my $ss = "screenshot_" . ($self->{mNumScreenShots}++) . ".png";
        $self->{mWindow}->writeContentsToFile($ss);
    }
    elsif ($key == OIS::Keyboard->KC_F1) {
        if ($self->{soundAlarm}) {
            Ogre::AL::SoundManager->getSingletonPtr->getSound("Siren")->stop();
            $self->{soundAlarm} = 0;
        }
        else {
            Ogre::AL::SoundManager->getSingletonPtr->getSound("Siren")->play();
            $self->{soundAlarm} = 1;
        }
    }
    elsif ($key == OIS::Keyboard->KC_UP || $key == OIS::Keyboard->KC_W) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setZ($self->{mDirection}->z - MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_DOWN || $key == OIS::Keyboard->KC_S) {
        $self->{mDirection}->setZ($self->{mDirection}->z + MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_LEFT || $key == OIS::Keyboard->KC_A) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setX($self->{mDirection}->x - MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_RIGHT || $key == OIS::Keyboard->KC_D) {
        $self->{mDirection}->setX($self->{mDirection}->x + MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_PGDOWN || $key == OIS::Keyboard->KC_Q) {
        $self->{mDirection}->setY($self->{mDirection}->y - MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_PGUP || $key == OIS::Keyboard->KC_E) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setY($self->{mDirection}->y + MOVEMENT_SPEED);
    }

    return 1;
}

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

    # xxx: I still haven't fixed these OIS constants....
    my $key = $arg->key;

    if ($key == OIS::Keyboard->KC_UP || $key == OIS::Keyboard->KC_W) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setZ($self->{mDirection}->z + MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_DOWN || $key == OIS::Keyboard->KC_S) {
        $self->{mDirection}->setZ($self->{mDirection}->z - MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_LEFT || $key == OIS::Keyboard->KC_A) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setX($self->{mDirection}->x + MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_RIGHT || $key == OIS::Keyboard->KC_D) {
        $self->{mDirection}->setX($self->{mDirection}->x - MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_PGDOWN || $key == OIS::Keyboard->KC_Q) {
        $self->{mDirection}->setY($self->{mDirection}->y + MOVEMENT_SPEED);
    }
    elsif ($key == OIS::Keyboard->KC_PGUP || $key == OIS::Keyboard->KC_E) {
        # a little weird due to crappy Perl bindings
        $self->{mDirection}->setY($self->{mDirection}->y - MOVEMENT_SPEED);
    }
}


1;


package OgreApp;

use strict;
use warnings;

use Ogre 0.35 qw(:SceneType :ShadowTechnique :GuiMetricsMode);
use Ogre::ColourValue;
use Ogre::Degree;
use Ogre::Light qw(:LightTypes);
use Ogre::OverlayManager;
use Ogre::Plane;
use Ogre::ResourceGroupManager qw(:GroupName);
use Ogre::Root;
use Ogre::Vector3;

use Ogre::AL::Sound;
use Ogre::AL::SoundManager;


sub new {
    my ($pkg) = @_;
    my $self = bless {
        root => Ogre::Root->new(),
        win => undef,
        sceneMgr => undef,
        camera => undef,
        soundManager => undef,
    }, $pkg;

    $self->setupResources();
    $self->configure();
    $self->chooseSceneManager();
    $self->createCamera();
    $self->createViewports();

    $self->{soundManager} = Ogre::AL::SoundManager->new();

    $self->createScene();

    my $listener = DeviceListener->new($self->{win}, $self->{camera}, $self->{sceneMgr});
    $self->{root}->addFrameListener($listener);

    return $self;
}

sub start {
    my ($self) = @_;
    $self->{root}->startRendering();
}

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

    my $mgr = $self->{sceneMgr};

    # xxx: I have no idea what voodoo makes this work in C++....
    # This is the API:
    # void setSkyBox(bool enable, const String &materialName, Real distance=5000, bool drawFirst=true, const Quaternion &orientation=Quaternion::IDENTITY, const String &groupName=ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)
    # $mgr->setSkyBox(1, "Sky", 5, 8, 4000);
    $mgr->setSkyBox(1, "Sky", 4000);
    $mgr->setAmbientLight(Ogre::ColourValue->new(0.5, 0.5, 0.5));
    # $mgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);

    my $node = $mgr->getRootSceneNode->createChildSceneNode("TowerNode");
    my $ent = $mgr->createEntity("Tower", "Tower.mesh");
    $node->attachObject($ent);

    $node = $mgr->getRootSceneNode->createChildSceneNode("HornNode");
    $ent = $mgr->createEntity("Horn", "Horn.mesh");
    $node->attachObject($ent);
    $node->setPosition(0, 23, 0);

    my $sound = $self->{soundManager}->createSound("Siren", "Siren.wav", 1);
    $sound->setGain(3.0);
    $sound->setInnerConeAngle(10);
    $sound->setOuterConeAngle(180);
    $sound->setOuterConeGain(0.2);
    $node->attachObject($sound);

    $node = $mgr->getRootSceneNode->createChildSceneNode("CameraNode");
    $node->setPosition(0, 6, 100);
    my $pitchNode = $node->createChildSceneNode("PitchNode");
    $pitchNode->attachObject($self->{camera});
    $pitchNode->attachObject($self->{soundManager}->getListener());

    # Create a ground plane
    my $plane = Ogre::Plane->new(Ogre::Vector3->new(0, 1, 0), 0);
    my $meshmgr = Ogre::MeshManager->getSingletonPtr();
    $meshmgr->createPlane("ground",
                          DEFAULT_RESOURCE_GROUP_NAME,
                          $plane, 15000, 15000, 20, 20, 1, 1, 5, 5,
                          Ogre::Vector3->new(0, 0, 1));
    $ent = $mgr->createEntity("GroundEntity", "ground");
    $mgr->getRootSceneNode->createChildSceneNode->attachObject($ent);
    $ent->setMaterialName("Ground");
    $ent->setCastShadows(0);

    my $light = $mgr->createLight("sun");
    $light->setType(LT_DIRECTIONAL);
    $light->setDirection(-1,-1,-1);

    # note: createOverlayContainer is specific to Perl Ogre
    my $overlayMgr = Ogre::OverlayManager->getSingletonPtr;
    my $panel = $overlayMgr->createOverlayContainer("Panel", "PanelName");
    $panel->setMetricsMode(GMM_PIXELS);
    $panel->setPosition(10, 10);
    $panel->setDimensions(100, 100);

    # note: createTextAreaOverlayElement is specific to Perl Ogre
    my $textArea = $overlayMgr->createTextAreaOverlayElement("TextArea", "TextAreaName");
    $textArea->setMetricsMode(GMM_PIXELS);
    $textArea->setPosition(0, 0);
    $textArea->setDimensions(100, 100);
    $textArea->setCharHeight(16);
    $textArea->setFontName("Arial");
    $textArea->setCaption("Hello, World!");

    my $overlay = $overlayMgr->create("AverageFps");
    $overlay->add2D($panel);
    $panel->addChild($textArea);
    $overlay->show();
}

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

    my $cf = Ogre::ConfigFile->new();
    $cf->load("resources.cfg");

    # note: this is a Perlish replacement for iterators used in C++
    my $secs = $cf->getSections();
    my $rgm = Ogre::ResourceGroupManager->getSingletonPtr();

    foreach my $sec (@$secs) {
        my $secName = $sec->{name};

        my $settings = $sec->{settings};
        foreach my $setting (@$settings) {
            my ($typeName, $archName) = @$setting;
            $rgm->addResourceLocation($archName, $typeName, $secName);
        }
    }

    $rgm->initialiseAllResourceGroups();
}

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

    # this shows an alternative to the way Ogre::ExampleApplication does it
    unless ($self->{root}->restoreConfig()) {
        unless ($self->{root}->showConfigDialog()) {
            exit;
        }
    }

    $self->{win} = $self->{root}->initialise(1, "Ogre Framework");
}

sub chooseSceneManager {
    my ($self) = @_;
    $self->{sceneMgr} = $self->{root}->createSceneManager(ST_GENERIC, "MainSceneManager");
}

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

    $self->{camera} = $self->{sceneMgr}->createCamera("SimpleCamera");
    $self->{camera}->setNearClipDistance(1.0);
}

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

    my $vp = $self->{win}->addViewport($self->{camera});
    $vp->setBackgroundColour(Ogre::ColourValue->new(0,0,0));
    $self->{camera}->setAspectRatio($vp->getActualWidth() / $vp->getActualHeight());
}


1;


package main;

# uncomment this if the packages are in separate files:
# use OgreApp;

OgreApp->new->start();
