Files
ogre-prototype/Game.cpp
2025-09-16 20:38:29 +03:00

796 lines
20 KiB
C++

#include <iostream>
#include <Ogre.h>
#include <OgreApplicationContext.h>
#include <OgreOverlaySystem.h>
#include <OgreOverlayManager.h>
#include <OgreImGuiOverlay.h>
#include <OgreImGuiInputListener.h>
#include <OgreTrays.h>
#include <OgreTimer.h>
#include <OgreMeshLodGenerator.h>
// #include "water/water.h"
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "TerrainModule.h"
#include "GUIModule.h"
#include "sound.h"
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
protected:
Ogre::SceneManager *mSceneManager;
virtual void _updateRenderQueue(Ogre::RenderQueue *queue) = 0;
public:
enum BoxPlane {
BP_FRONT = 0,
BP_BACK = 1,
BP_LEFT = 2,
BP_RIGHT = 3,
BP_UP = 4,
BP_DOWN = 5
};
SkyRenderer(Ogre::SceneManager *owner)
: mSceneManager(owner)
, mSceneNode(0)
, mEnabled(false)
{
}
virtual ~SkyRenderer()
{
setEnabled(false);
if (mSceneNode)
mSceneManager->destroySceneNode(mSceneNode);
}
Ogre::SceneNode *mSceneNode;
bool mEnabled;
void setEnabled(bool enable)
{
if (enable == mEnabled)
return;
mEnabled = enable;
enable ? mSceneManager->addListener(this) :
mSceneManager->removeListener(this);
}
void
postFindVisibleObjects(Ogre::SceneManager *source,
Ogre::SceneManager::IlluminationRenderStage irs,
Ogre::Viewport *vp) override
{
// Queue skies, if viewport seems it
if (!vp->getSkiesEnabled() ||
irs == Ogre::SceneManager::IRS_RENDER_TO_TEXTURE)
return;
if (!mEnabled || !mSceneNode)
return;
// Update nodes
// Translate the box by the camera position (constant distance)
mSceneNode->setPosition(vp->getCamera()->getDerivedPosition());
_updateRenderQueue(source->getRenderQueue());
}
};
class SkyBoxRenderer : public SkyRenderer {
std::unique_ptr<Ogre::ManualObject> mSkyBoxObj;
Ogre::Quaternion mSkyBoxOrientation;
void _updateRenderQueue(Ogre::RenderQueue *queue) override
{
if (mSkyBoxObj->isVisible()) {
mSkyBoxObj->_updateRenderQueue(queue);
}
}
public:
SkyBoxRenderer(Ogre::SceneManager *owner)
: SkyRenderer(owner)
{
}
Ogre::SceneManager::SkyBoxGenParameters mSkyBoxGenParameters;
void create(const Ogre::String &materialName, Ogre::Real distance,
uint8_t renderQueue, const Ogre::Quaternion &orientation,
const Ogre::String &groupName)
{
Ogre::MaterialPtr m =
Ogre::MaterialManager::getSingleton().getByName(
materialName, groupName);
OgreAssert(m, "Sky box material '" + materialName +
"' not found.");
// Ensure loaded
m->load();
bool valid = m->getBestTechnique() &&
m->getBestTechnique()->getNumPasses();
#if 0
if (valid) {
Pass *pass = m->getBestTechnique()->getPass(0);
valid = valid && pass->getNumTextureUnitStates() &&
pass->getTextureUnitState(0)->getTextureType() ==
TEX_TYPE_CUBE_MAP;
}
if (!valid) {
LogManager::getSingleton().logWarning(
"skybox material " + materialName +
" is not supported, defaulting");
m = MaterialManager::getSingleton().getDefaultSettings();
}
#endif
OgreAssert(valid, "Bad material" + materialName);
// Create node
mSceneNode = mSceneManager->createSceneNode();
// Create object
mSkyBoxObj = std::make_unique<Ogre::ManualObject>("SkyBox");
mSkyBoxObj->setCastShadows(false);
mSceneNode->attachObject(mSkyBoxObj.get());
mSkyBoxObj->setRenderQueueGroup(renderQueue);
mSkyBoxObj->begin(materialName,
Ogre::RenderOperation::OT_TRIANGLE_STRIP,
groupName);
// rendering cube, only using 14 vertices
const Ogre::Vector3 cube_strip[14] = {
{ -1.f, 1.f, 1.f }, // Front-top-left
{ 1.f, 1.f, 1.f }, // Front-top-right
{ -1.f, -1.f, 1.f }, // Front-bottom-left
{ 1.f, -1.f, 1.f }, // Front-bottom-right
{ 1.f, -1.f, -1.f }, // Back-bottom-right
{ 1.f, 1.f, 1.f }, // Front-top-right
{ 1.f, 1.f, -1.f }, // Back-top-right
{ -1.f, 1.f, 1.f }, // Front-top-left
{ -1.f, 1.f, -1.f }, // Back-top-left
{ -1.f, -1.f, 1.f }, // Front-bottom-left
{ -1.f, -1.f, -1.f }, // Back-bottom-left
{ 1.f, -1.f, -1.f }, // Back-bottom-right
{ -1.f, 1.f, -1.f }, // Back-top-left
{ 1.f, 1.f, -1.f } // Back-top-right
};
for (const auto &vtx : cube_strip) {
mSkyBoxObj->position(orientation * (vtx * distance));
// Note UVs mirrored front/back
mSkyBoxObj->textureCoord(vtx.normalisedCopy() *
Ogre::Vector3(1, 1, -1));
}
mSkyBoxObj->end();
mSkyBoxGenParameters.skyBoxDistance = distance;
}
};
class App;
class KeyboardListener : public OgreBites::InputListener {
App *mApp;
uint32_t control;
ECS::Vector2 mouse;
float wheel_y;
bool mouse_moved, wheel_moved;
float mInitDelay;
public:
Ogre::Timer fps_timer;
bool fast;
KeyboardListener(App *app)
: OgreBites::InputListener()
, mApp(app)
, fast(false)
, control(0)
, mouse({ 0, 0 })
, wheel_y(0.0f)
, mouse_moved(false)
, wheel_moved(false)
, mInitDelay(1.0)
{
}
bool isGuiEnabled()
{
if (!ECS::get().has<ECS::GUI>())
return false;
return (ECS::get().get<ECS::GUI>().enabled);
}
void setGuiEnabled(bool value)
{
if (!ECS::get().has<ECS::GUI>())
return;
ECS::get().get_mut<ECS::GUI>().enabled = value;
ECS::get().modified<ECS::GUI>();
}
bool keyPressed(const OgreBites::KeyboardEvent &evt) override
{
bool updated = false;
if (isGuiEnabled())
return false;
std::cout << "GUI not enabled\n";
if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
OgreAssert(ECS::get().has<ECS::GUI>(), "");
setGuiEnabled(true);
if (ECS::get().has<ECS::GUI>())
ECS::get<ECS::GUI>().setWindowGrab(false);
return true;
}
OgreBites::Keycode key = evt.keysym.sym;
if (key == 'w')
control |= 1;
else if (key == 'a')
control |= 2;
else if (key == 's')
control |= 4;
else if (key == 'd')
control |= 8;
else if (key == OgreBites::SDLK_LSHIFT)
control |= 16;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT)
return true;
return false;
}
bool keyReleased(const OgreBites::KeyboardEvent &evt) override
{
OgreBites::Keycode key = evt.keysym.sym;
if (isGuiEnabled())
return false;
if (key == 'w')
control &= ~1;
else if (key == 'a')
control &= ~2;
else if (key == 's')
control &= ~4;
else if (key == 'd')
control &= ~8;
else if (key == OgreBites::SDLK_LSHIFT)
control &= ~16;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT)
return true;
return false;
}
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override
{
if (isGuiEnabled())
return false;
mouse.x = evt.xrel;
mouse.y = evt.yrel;
mouse_moved = true;
/* no special mouse handling */
return true;
}
bool mouseWheelRolled(const OgreBites::MouseWheelEvent &evt) override
{
if (isGuiEnabled())
return false;
/* no special mouse wheel handling */
wheel_y = evt.y;
wheel_moved = true;
return true;
}
bool mousePressed(const OgreBites::MouseButtonEvent &evt) override
{
std::cout << "Mouse press " << (int)evt.button << " "
<< (int)evt.clicks << "\n";
if ((int)evt.button == 1)
control |= 256;
else
control &= ~256;
return false;
}
void update(float delta)
{
return;
}
void frameRendered(const Ogre::FrameEvent &evt) override;
};
class App : public OgreBites::ApplicationContext {
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Camera *mCamera;
Ogre::Real mPivotPitch;
Ogre::SceneManager *mScnMgr;
Ogre::Viewport *mViewport;
SkyBoxRenderer *sky;
bool mGrab;
KeyboardListener mKbd;
bool enabldDbgDraw;
public:
App()
: OgreBites::ApplicationContext("ChoroGame")
, mKbd(this)
, mDynWorld(new Ogre::Bullet::DynamicsWorld(
Ogre::Vector3(0, -9.8, 0)))
, mGrab(false)
, enabldDbgDraw(false)
{
}
virtual ~App()
{
}
void setup()
{
OgreBites::ApplicationContext::setup();
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = root->createSceneManager();
mScnMgr = scnMgr;
Ogre::OverlaySystem *pOverlaySystem = getOverlaySystem();
mScnMgr->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
mDbgDraw.reset(new Ogre::Bullet::DebugDrawer(
mScnMgr->getRootSceneNode(), mDynWorld->getBtWorld()));
}
bool isWindowGrab()
{
return mGrab;
}
void locateResources() override
{
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Water", true);
OgreBites::ApplicationContext::locateResources();
}
void loadResources() override
{
}
void setWindowGrab(bool grab = true)
{
mGrab = grab;
ApplicationContextBase::setWindowGrab(grab);
}
void initCamera()
{
mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode(
"CameraNode");
mCameraNode->setPosition(0, 2, 3);
mCameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
// create the camera
mCamera = mScnMgr->createCamera("fps_camera");
mCamera->setNearClipDistance(0.05f);
mCamera->setAutoAspectRatio(true);
mCameraNode->attachObject(mCamera);
// and tell it to render into the main window
mViewport = getRenderWindow()->addViewport(mCamera);
mCameraPivot =
mScnMgr->getRootSceneNode()->createChildSceneNode(
"FPSCameraPivot");
mCameraGoal = mCameraPivot->createChildSceneNode(
"FPSCameraGoal", Ogre::Vector3(0, 2, 3));
mCameraNode->setPosition(mCameraPivot->getPosition() +
mCameraGoal->getPosition());
mCameraPivot->setFixedYawAxis(true);
mCameraGoal->setFixedYawAxis(true);
mCameraNode->setFixedYawAxis(true);
// our model is quite small, so reduce the clipping planes
mCamera->setNearClipDistance(0.1f);
mCamera->setFarClipDistance(800);
mPivotPitch = 0;
}
void configure()
{
std::cout << "Startup" << "\n";
initApp();
std::cout << "Set up RTSS" << "\n";
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = getSceneManager();
// register our scene with the RTSS
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(scnMgr);
setWindowGrab(true);
std::cout << "Init camera" << "\n";
initCamera();
std::cout << "Set up water" << "\n";
std::cout << "Set up cursor" << "\n";
Ogre::ResourceGroupManager::getSingleton()
.initialiseAllResourceGroups();
// OgreBites::ApplicationContext::loadResources();
// setupCursor();
std::cout << "Setup input" << "\n";
setupInput();
std::cout << "Create content" << "\n";
createContent();
std::cout << "Setup done" << "\n";
mDbgDraw->setDebugMode(mDbgDraw->getDebugMode() |
btIDebugDraw::DBG_DrawContactPoints);
}
Ogre::SceneManager *getSceneManager()
{
return mScnMgr;
}
Ogre::Timer mTerrainUpd;
bool isTerrainReady()
{
if (ECS::get().has<ECS::Terrain>())
return ECS::get().get<ECS::Terrain>().mTerrainReady;
return false;
}
// TODO: implement rough water level calculation
float getWaterLevel(const Ogre::Vector3 &position)
{
Ogre::Vector3::UNIT_Y;
float etime =
Ogre::ControllerManager::getSingleton().getElapsedTime();
return 0.0f;
}
void updateWorld(float delta)
{
mDynWorld->getBtWorld()->stepSimulation(delta, 4);
/* Update window grab */
if (ECS::get().has<ECS::GUI>() &&
ECS::get().get<ECS::GUI>().grabChanged) {
setWindowGrab(ECS::get().get<ECS::GUI>().grab);
ECS::get().get_mut<ECS::GUI>().grabChanged = false;
ECS::get().modified<ECS::GUI>();
}
ECS::update(delta);
if (enabldDbgDraw)
mDbgDraw->update();
}
class InputListenerChainFlexible : public OgreBites::InputListener {
protected:
std::vector<OgreBites::InputListener *> mListenerChain;
public:
InputListenerChainFlexible()
{
}
InputListenerChainFlexible(std::vector<InputListener *> chain)
: mListenerChain(chain)
{
}
void add(OgreBites::InputListener *listener)
{
mListenerChain.push_back(listener);
}
void erase(OgreBites::InputListener *listener)
{
mListenerChain.erase(std::find(mListenerChain.begin(),
mListenerChain.end(),
listener));
}
bool empty() const
{
return mListenerChain.empty();
}
InputListenerChainFlexible &
operator=(const InputListenerChainFlexible &o)
{
mListenerChain = o.mListenerChain;
return *this;
}
void frameRendered(const Ogre::FrameEvent &evt) override
{
for (auto listener : mListenerChain)
listener->frameRendered(evt);
}
bool keyPressed(const OgreBites::KeyboardEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->keyPressed(evt))
return true;
}
return false;
}
bool keyReleased(const OgreBites::KeyboardEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->keyReleased(evt))
return true;
}
return false;
}
bool touchMoved(const OgreBites::TouchFingerEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->touchMoved(evt))
return true;
}
return false;
}
bool
touchPressed(const OgreBites::TouchFingerEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->touchPressed(evt))
return true;
}
return false;
}
bool
touchReleased(const OgreBites::TouchFingerEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->touchReleased(evt))
return true;
}
return false;
}
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->mouseMoved(evt))
return true;
}
return false;
}
bool
mouseWheelRolled(const OgreBites::MouseWheelEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->mouseWheelRolled(evt))
return true;
}
return false;
}
bool
mousePressed(const OgreBites::MouseButtonEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->mousePressed(evt))
return true;
}
return false;
}
bool
mouseReleased(const OgreBites::MouseButtonEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->mouseReleased(evt))
return true;
}
return false;
}
bool textInput(const OgreBites::TextInputEvent &evt) override
{
for (auto listner : mListenerChain) {
if (listner->textInput(evt))
return true;
}
return false;
}
};
flecs::entity input_update;
flecs::entity find_wait_gui;
void setupInput()
{
}
void createContent()
{
int i;
sky = new SkyBoxRenderer(getSceneManager());
bool drawFirst = true;
uint8_t renderQueue = drawFirst ?
Ogre::RENDER_QUEUE_SKIES_EARLY :
Ogre::RENDER_QUEUE_SKIES_LATE;
sky->create("Skybox/Dynamic", 450, renderQueue,
Ogre::Quaternion::IDENTITY,
Ogre::ResourceGroupManager::
AUTODETECT_RESOURCE_GROUP_NAME);
sky->setEnabled(true);
Ogre::MaterialPtr m =
Ogre::MaterialManager::getSingleton().getByName(
"Skybox/Dynamic", "General");
OgreAssert(m, "Sky box material not found.");
m->load();
ECS::setup(mScnMgr, mDynWorld.get(), mCameraNode, mCamera,
getRenderWindow());
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::get()
.observer<ECS::GUI>("UpdateGrab")
.event(flecs::OnSet)
.each([this](flecs::entity e, ECS::GUI &gui) {
if (gui.grabChanged)
setWindowGrab(gui.grab);
std::cout << "grab: " << gui.grab << "\n";
std::cout << "GUI enabled: " << gui.enabled
<< "\n";
});
ECS::get()
.observer<ECS::App>("UpdateInputListener")
.event(flecs::OnSet)
.each([this](flecs::entity e, ECS::App &app) {
if (app.mInput)
removeInputListener(app.mInput);
delete app.mInput;
app.mInput =
OGRE_NEW OgreBites::InputListenerChain(
app.listeners);
addInputListener(app.mInput);
});
#if 0
ECS::get()
.observer<ECS::GUI, ECS::App>("SetInputListener2")
.event(flecs::OnSet)
.each([this](ECS::GUI &gui, ECS::App &app) {
if (gui.mGuiInpitListener &&
app.listeners.size() == 1) {
app.listeners.clear();
app.listeners.push_back(
gui.mGuiInpitListener);
app.listeners.push_back(&mKbd);
ECS::modified<ECS::App>();
}
});
#endif
#if 0
input_update =
ECS::get()
.system<ECS::GUI, ECS::App>("SetInputListener")
.kind(flecs::OnUpdate)
.each([this](ECS::GUI &gui, ECS::App &app) {
if (app.listeners.size() < 2 &&
gui.mGuiInpitListener) {
OgreBites::InputListener *guiListener =
gui.mGuiInpitListener;
if (guiListener) {
app.listeners.clear();
app.listeners.push_back(
guiListener);
app.listeners.push_back(
&mKbd);
std::cout
<< "input update complete\n";
gui.mGuiInpitListener =
guiListener;
if (app.mInput)
removeInputListener(
app.mInput);
delete app.mInput;
app.mInput = new OgreBites::
InputListenerChain(
app.listeners);
addInputListener(
app.mInput);
std::cout
<< "update listeners: "
<< app.listeners
.size()
<< "\n";
if (app.listeners
.size() ==
2)
OgreAssert(
app.listeners.size() ==
2,
"");
input_update.disable();
OgreAssert(false, "");
} else {
app.listeners.clear();
app.listeners.push_back(
&mKbd);
}
} else
input_update.disable();
std::cout << "input update "
<< app.listeners.size()
<< "\n";
});
#endif
ECS::get().set<ECS::App>(
{ initialiseImGui(),
nullptr,
{ getImGuiInputListener(), &mKbd } });
Sound::setup();
Sound::ding();
}
void create_entity_node(const Ogre::String &name, int key)
{
Ogre::Entity *ent = mScnMgr->createEntity(name);
Ogre::SceneNode *pnode =
mScnMgr->getRootSceneNode()->createChildSceneNode(
"ent:" + name +
Ogre::StringConverter::toString(key),
mCameraPivot->getPosition(),
mCameraPivot->getOrientation());
pnode->attachObject(ent);
Ogre::Quaternion q = pnode->getOrientation();
Ogre::Radian yaw = q.getYaw();
Ogre::Quaternion nq(yaw, Ogre::Vector3(0, 1, 0));
pnode->setOrientation(nq);
set_gui_active(false);
ECS::get<ECS::GUI>().setWindowGrab(true);
}
bool get_gui_active()
{
return ECS::get().get<ECS::GUI>().enabled;
}
void set_gui_active(bool active)
{
ECS::get().get_mut<ECS::GUI>().enabled = active;
ECS::get().modified<ECS::GUI>();
}
Ogre::Camera *getCamera()
{
return mCamera;
}
flecs::entity getPlayer() const
{
flecs::entity player =
ECS::get().lookup("ECS::CharacterModule::player");
return player;
}
void enableDbgDraw(bool enable)
{
enabldDbgDraw = enable;
}
bool isEnabledDbgDraw() const
{
return enabldDbgDraw;
}
};
void KeyboardListener::frameRendered(const Ogre::FrameEvent &evt)
{
if (fps_timer.getMilliseconds() > 1000.0f) {
std::cout << "FPS: "
<< mApp->getRenderWindow()->getStatistics().lastFPS
<< " ";
std::cout << "Draw calls: "
<< mApp->getRenderWindow()->getStatistics().batchCount
<< " ";
fps_timer.reset();
std::cout << "Drops: "
<< mApp->getRenderWindow()
->getStatistics()
.vBlankMissCount
<< "\n";
fps_timer.reset();
}
update(evt.timeSinceLastFrame);
if (!isGuiEnabled() ||
(isGuiEnabled() && ECS::get<ECS::GUI>().narrationBox)) {
mApp->updateWorld(evt.timeSinceLastFrame);
if (mInitDelay >= 0.0f)
mInitDelay -= evt.timeSinceLastFrame;
}
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
}
}
int main()
{
App ctx;
ctx.configure();
// ctx.runRenderingSettingsDialog();
// get a pointer to the already created root
// register for input events
// KeyHandler keyHandler;
// ctx.addInputListener(&keyHandler);
ctx.enableDbgDraw(false);
ctx.getRoot()->startRendering();
ctx.setWindowGrab(false);
ctx.closeApp();
return 0;
}