main.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #include <QGuiApplication>
  2. #include <QQmlApplicationEngine>
  3. #include <QOpenGLContext>
  4. #include <QSurfaceFormat>
  5. #include <QDebug>
  6. #include <QDir>
  7. #include <QQuickWindow>
  8. #include <QQmlContext>
  9. #include <QSGRendererInterface>
  10. #include "engine/core/world.h"
  11. #include "engine/core/component.h"
  12. #include "render/gl/renderer.h"
  13. #include "render/gl/camera.h"
  14. #include "game/systems/movement_system.h"
  15. #include "game/systems/combat_system.h"
  16. #include "game/systems/selection_system.h"
  17. // #include "game/systems/ai_system.h" // keep if you have AISystem; remove if not
  18. class GameEngine : public QObject {
  19. Q_OBJECT
  20. public:
  21. GameEngine() {
  22. m_world = std::make_unique<Engine::Core::World>();
  23. m_renderer = std::make_unique<Render::GL::Renderer>();
  24. m_camera = std::make_unique<Render::GL::Camera>();
  25. m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
  26. m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
  27. m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
  28. m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
  29. m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
  30. setupTestScene();
  31. }
  32. Q_INVOKABLE void onMapClicked(qreal sx, qreal sy) {
  33. if (!m_window) return;
  34. ensureInitialized();
  35. QVector3D hit;
  36. if (!screenToGround(QPointF(sx, sy), hit)) return;
  37. if (auto* entity = m_world->getEntity(m_playerUnitId)) {
  38. if (auto* move = entity->getComponent<Engine::Core::MovementComponent>()) {
  39. move->targetX = hit.x();
  40. move->targetY = hit.z();
  41. move->hasTarget = true;
  42. }
  43. }
  44. }
  45. void setWindow(QQuickWindow* w) { m_window = w; }
  46. void initialize() {
  47. // Must be called with a current, valid GL context.
  48. QOpenGLContext* ctx = QOpenGLContext::currentContext();
  49. if (!ctx || !ctx->isValid()) {
  50. qWarning() << "GameEngine::initialize called without a current, valid OpenGL context";
  51. return;
  52. }
  53. if (!m_renderer->initialize()) {
  54. qWarning() << "Failed to initialize renderer";
  55. return;
  56. }
  57. m_renderer->setCamera(m_camera.get());
  58. m_camera->setRTSView(QVector3D(0, 0, 0), 15.0f, 45.0f);
  59. m_camera->setPerspective(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
  60. m_initialized = true;
  61. qDebug() << "Game engine initialized successfully";
  62. }
  63. void ensureInitialized() { if (!m_initialized) initialize(); }
  64. void update(float dt) {
  65. if (m_world) m_world->update(dt);
  66. }
  67. void render() {
  68. if (!m_renderer || !m_world || !m_initialized) return;
  69. if (m_window) {
  70. const int vpW = int(m_window->width() * m_window->effectiveDevicePixelRatio());
  71. const int vpH = int(m_window->height() * m_window->effectiveDevicePixelRatio());
  72. m_renderer->setViewport(vpW, vpH);
  73. }
  74. m_renderer->beginFrame();
  75. m_renderer->renderWorld(m_world.get());
  76. m_renderer->endFrame();
  77. }
  78. private:
  79. void setupTestScene() {
  80. auto entity = m_world->createEntity();
  81. m_playerUnitId = entity->getId();
  82. auto transform = entity->addComponent<Engine::Core::TransformComponent>();
  83. transform->position = {0.0f, 0.0f, 0.0f};
  84. transform->scale = {0.5f, 0.5f, 0.5f};
  85. transform->rotation.x = -90.0f; // lay quad on XZ plane
  86. auto renderable = entity->addComponent<Engine::Core::RenderableComponent>("", "");
  87. renderable->visible = true;
  88. auto unit = entity->addComponent<Engine::Core::UnitComponent>();
  89. unit->unitType = "archer";
  90. unit->health = 80;
  91. unit->maxHealth = 80;
  92. unit->speed = 3.0f;
  93. entity->addComponent<Engine::Core::MovementComponent>();
  94. qDebug() << "Test scene created with 1 archer (entity" << m_playerUnitId << ")";
  95. }
  96. bool screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
  97. if (!m_window || !m_camera) return false;
  98. float w = float(m_window->width());
  99. float h = float(m_window->height());
  100. if (w <= 0 || h <= 0) return false;
  101. float x = (2.0f * float(screenPt.x()) / w) - 1.0f;
  102. float y = 1.0f - (2.0f * float(screenPt.y()) / h);
  103. bool ok = false;
  104. QMatrix4x4 invVP = (m_camera->getProjectionMatrix() * m_camera->getViewMatrix()).inverted(&ok);
  105. if (!ok) return false;
  106. QVector4D nearClip(x, y, 0.0f, 1.0f);
  107. QVector4D farClip (x, y, 1.0f, 1.0f);
  108. QVector4D nearWorld4 = invVP * nearClip;
  109. QVector4D farWorld4 = invVP * farClip;
  110. if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f) return false;
  111. QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
  112. QVector3D rayEnd = (farWorld4 / farWorld4.w()).toVector3D();
  113. QVector3D rayDir = (rayEnd - rayOrigin).normalized();
  114. if (qFuzzyIsNull(rayDir.y())) return false;
  115. float t = -rayOrigin.y() / rayDir.y();
  116. if (t < 0.0f) return false;
  117. outWorld = rayOrigin + rayDir * t;
  118. return true;
  119. }
  120. std::unique_ptr<Engine::Core::World> m_world;
  121. std::unique_ptr<Render::GL::Renderer> m_renderer;
  122. std::unique_ptr<Render::GL::Camera> m_camera;
  123. std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
  124. QQuickWindow* m_window = nullptr;
  125. Engine::Core::EntityID m_playerUnitId = 0;
  126. bool m_initialized = false;
  127. };
  128. int main(int argc, char *argv[])
  129. {
  130. // Force desktop OpenGL + GLX path BEFORE any window is created.
  131. if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY") && qEnvironmentVariableIsSet("DISPLAY")) {
  132. qputenv("QT_QPA_PLATFORM", "xcb"); // prefer XCB/GLX over Wayland/EGL when XWayland is present
  133. }
  134. qputenv("QT_OPENGL", "desktop"); // desktop GL, not GLES/EGL
  135. qputenv("QSG_RHI_BACKEND", "opengl"); // OpenGL RHI
  136. QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
  137. // Request desktop GL 3.3 core.
  138. QSurfaceFormat fmt;
  139. fmt.setVersion(3, 3);
  140. fmt.setProfile(QSurfaceFormat::CoreProfile);
  141. fmt.setDepthBufferSize(24);
  142. fmt.setStencilBufferSize(8);
  143. fmt.setSamples(0);
  144. QSurfaceFormat::setDefaultFormat(fmt);
  145. QGuiApplication app(argc, argv);
  146. auto gameEngine = new GameEngine();
  147. QQmlApplicationEngine engine;
  148. // Expose to QML BEFORE loading (safer if QML binds early).
  149. engine.rootContext()->setContextProperty("game", gameEngine);
  150. engine.load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
  151. if (engine.rootObjects().isEmpty()) {
  152. qWarning() << "Failed to load QML file";
  153. return -1;
  154. }
  155. QObject* rootObj = engine.rootObjects().first();
  156. QQuickWindow* window = qobject_cast<QQuickWindow*>(rootObj);
  157. if (!window) window = rootObj->findChild<QQuickWindow*>();
  158. if (!window) {
  159. qWarning() << "No QQuickWindow found for OpenGL initialization.";
  160. return -2;
  161. }
  162. // For now, draw GL ON TOP of QML (simplest to verify).
  163. window->setColor(Qt::black);
  164. gameEngine->setWindow(window);
  165. // Informative logging (no current-context check here).
  166. QObject::connect(window, &QQuickWindow::sceneGraphInitialized, window, [window]() {
  167. if (auto *ri = window->rendererInterface()) {
  168. auto api = ri->graphicsApi();
  169. const char* name = api == QSGRendererInterface::OpenGLRhi ? "OpenGLRhi" :
  170. api == QSGRendererInterface::VulkanRhi ? "VulkanRhi" :
  171. api == QSGRendererInterface::Direct3D11Rhi ? "D3D11Rhi" :
  172. api == QSGRendererInterface::MetalRhi ? "MetalRhi" :
  173. api == QSGRendererInterface::Software ? "Software" : "Unknown";
  174. qInfo() << "QSG graphicsApi:" << name;
  175. }
  176. });
  177. QObject::connect(window, &QQuickWindow::sceneGraphError, &app,
  178. [&](QQuickWindow::SceneGraphError, const QString &msg){
  179. qCritical() << "Failed to initialize OpenGL scene graph:" << msg;
  180. app.exit(3);
  181. });
  182. // Draw AFTER Qt Quick so GL is visible on top.
  183. QObject::connect(window, &QQuickWindow::afterRendering, gameEngine, [gameEngine, window]() {
  184. auto *ri = window->rendererInterface();
  185. const bool isGL = ri && ri->graphicsApi() == QSGRendererInterface::OpenGLRhi;
  186. if (!isGL) return;
  187. window->beginExternalCommands(); // makes the GL context current for external GL
  188. QOpenGLContext* ctx = QOpenGLContext::currentContext();
  189. if (!ctx || !ctx->isValid()) { window->endExternalCommands(); return; }
  190. gameEngine->ensureInitialized();
  191. gameEngine->update(1.0f / 60.0f);
  192. gameEngine->render();
  193. window->endExternalCommands();
  194. }, Qt::DirectConnection);
  195. qDebug() << "Application started successfully";
  196. qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
  197. return app.exec();
  198. }
  199. #include "main.moc"