diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e1c70a..6cd0612 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,7 @@ jobs: mingw-w64-${{ matrix.env }}-pugixml mingw-w64-${{ matrix.env }}-ogre3d mingw-w64-${{ matrix.env }}-qt6 + mingw-w64-${{ matrix.env }}-bullet - name: Generate Ninja build files run: cmake -G Ninja -B build -DCI=ON -DCMAKE_BUILD_TYPE=${{ matrix.configuration == 'Release' && 'MinSizeRel' || matrix.configuration }} . diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dc6607..35b9a01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ add_compile_options(-Wno-attributes -Wno-return-type) # Ignore warnings generate find_package(Boost REQUIRED) find_package(OGRE REQUIRED COMPONENTS Bites CONFIG) find_package(pugixml REQUIRED) +find_package(Bullet CONFIG REQUIRED) +set(RNR_BULLET_LIBRARIES LinearMath Bullet3Common BulletDynamics BulletSoftBody BulletCollision BulletInverseDynamics) add_subdirectory(Projects) diff --git a/Dependencies/Luau b/Dependencies/Luau index 2181591..e25de95 160000 --- a/Dependencies/Luau +++ b/Dependencies/Luau @@ -1 +1 @@ -Subproject commit 218159140c7d79ae745e646da721d12331f536f5 +Subproject commit e25de95445f2d635a125ab463426bb7fda017093 diff --git a/Projects/Client/Common/Source/OgreWidget.cpp b/Projects/Client/Common/Source/OgreWidget.cpp index e997a16..f98dabe 100644 --- a/Projects/Client/Common/Source/OgreWidget.cpp +++ b/Projects/Client/Common/Source/OgreWidget.cpp @@ -76,7 +76,7 @@ namespace RNR if (!std::filesystem::is_directory("ShaderCache") || !std::filesystem::exists("ShaderCache")) { std::filesystem::create_directory("ShaderCache"); } - + ogreShaderGen->setShaderCachePath("ShaderCache/"); ogreShaderGen->addSceneManager(ogreSceneManager); @@ -220,7 +220,11 @@ namespace RNR void OgreWidget::closeEvent(QCloseEvent* event) { + delete world; + ogreWindow->destroy(); + ogreRoot->destroySceneManager(ogreSceneManager); + ogreRoot->shutdown(); } QPaintEngine* OgreWidget::paintEngine() const diff --git a/Projects/Client/Studio/Source/MainWindow.cpp b/Projects/Client/Studio/Source/MainWindow.cpp index 8cc231b..b0a7e52 100644 --- a/Projects/Client/Studio/Source/MainWindow.cpp +++ b/Projects/Client/Studio/Source/MainWindow.cpp @@ -138,6 +138,8 @@ void MainWindow::playSolo() RNR::Players* players = (RNR::Players*)this->ogreWidget->world->getDatamodel()->getService("Players"); RNR::Player* player = players->createLocalPlayer(0); + if(!player) + return; player->setName(QInputDialog::getText(this, "Player Name", "Enter your player name").toLocal8Bit().data()); player->loadCharacter(); diff --git a/Projects/Engine/CMakeLists.txt b/Projects/Engine/CMakeLists.txt index 5265659..f436faa 100644 --- a/Projects/Engine/CMakeLists.txt +++ b/Projects/Engine/CMakeLists.txt @@ -67,5 +67,7 @@ add_library(Engine STATIC Source/Rendering/Adorn.cpp ) -target_include_directories(Engine PUBLIC ${Boost_INCLUDE_DIRS} Header/) -target_link_libraries(Engine PUBLIC ${Boost_LIBRARIES} pugixml OgreBites Luau.Analysis Luau.Ast Luau.Compiler Luau.VM) \ No newline at end of file +target_include_directories(Engine PUBLIC ${Boost_INCLUDE_DIRS} ${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIR} Header/) +target_compile_definitions(Engine PUBLIC ${BULLET_DEFINITIONS}) +target_link_directories(Engine PUBLIC ${Bullet_DIR}/${BULLET_ROOT_DIR}/${BULLET_LIBRARY_DIRS}) +target_link_libraries(Engine PUBLIC ${Boost_LIBRARIES} ${RNR_BULLET_LIBRARIES} pugixml OgreBites Luau.Analysis Luau.Ast Luau.Compiler Luau.VM) \ No newline at end of file diff --git a/Projects/Engine/Header/App/Humanoid/Humanoid.hpp b/Projects/Engine/Header/App/Humanoid/Humanoid.hpp index 197acff..555f146 100644 --- a/Projects/Engine/Header/App/Humanoid/Humanoid.hpp +++ b/Projects/Engine/Header/App/Humanoid/Humanoid.hpp @@ -20,6 +20,7 @@ namespace RNR PartInstance* getTorso(); PartInstance* getHead(); + void inputFrame(float dx, float dy); void createHealthBar(); float getHealth() { return m_health; }; diff --git a/Projects/Engine/Header/App/InputManager.hpp b/Projects/Engine/Header/App/InputManager.hpp index 8274d1c..d4dc866 100644 --- a/Projects/Engine/Header/App/InputManager.hpp +++ b/Projects/Engine/Header/App/InputManager.hpp @@ -15,6 +15,8 @@ namespace RNR { World* m_world; std::vector scancodes_down; + float m_mouseDX; + float m_mouseDY; protected: MouseState state; public: diff --git a/Projects/Engine/Header/App/V8/DataModel/Camera.hpp b/Projects/Engine/Header/App/V8/DataModel/Camera.hpp index 0b66cf0..4da20ea 100644 --- a/Projects/Engine/Header/App/V8/DataModel/Camera.hpp +++ b/Projects/Engine/Header/App/V8/DataModel/Camera.hpp @@ -13,6 +13,7 @@ namespace RNR private: CoordinateFrame m_cframe; CoordinateFrame m_focus; + Ogre::Radian m_yaw; virtual void deserializeProperty(char* prop_name, pugi::xml_node prop); virtual void addProperties(std::vector& properties); public: @@ -24,6 +25,7 @@ namespace RNR virtual std::string getClassName() { return "Camera"; } CoordinateFrame& getCFrame() { return m_cframe; }; CoordinateFrame& getFocus() { return m_focus; } + Ogre::Radian getYaw() { return m_yaw; } void setCFrame(CoordinateFrame cframe) { m_cframe = cframe; }; void setFocus(CoordinateFrame focus) { m_focus = focus; } bool zoom(float distance); diff --git a/Projects/Engine/Header/App/V8/Tree/Instance.hpp b/Projects/Engine/Header/App/V8/Tree/Instance.hpp index ebc731e..f261e40 100644 --- a/Projects/Engine/Header/App/V8/Tree/Instance.hpp +++ b/Projects/Engine/Header/App/V8/Tree/Instance.hpp @@ -37,7 +37,7 @@ namespace RNR public: Instance(); - ~Instance(); + virtual ~Instance(); virtual std::vector getProperties(); void deserializeXmlProperty(pugi::xml_node prop); // TODO: eventually replace this with a method that uses getProperties diff --git a/Projects/Engine/Header/App/V8/Tree/PVInstance.hpp b/Projects/Engine/Header/App/V8/Tree/PVInstance.hpp index 94508b1..9f0739b 100644 --- a/Projects/Engine/Header/App/V8/Tree/PVInstance.hpp +++ b/Projects/Engine/Header/App/V8/Tree/PVInstance.hpp @@ -15,6 +15,7 @@ namespace RNR CoordinateFrame& getCFrame() { return m_cframe; }; void setCFrame(CoordinateFrame cframe) { m_cframe = cframe; }; + Ogre::Vector3 relativePositionTo(PVInstance* point); virtual std::string getClassName() { return "PVInstance"; } Ogre::Vector3 getPosition() { return m_cframe.getPosition(); } diff --git a/Projects/Engine/Header/App/V8/World/World.hpp b/Projects/Engine/Header/App/V8/World/World.hpp index ff2e87f..0537c08 100644 --- a/Projects/Engine/Header/App/V8/World/World.hpp +++ b/Projects/Engine/Header/App/V8/World/World.hpp @@ -11,6 +11,9 @@ #include #include #include +#include "LinearMath/btVector3.h" +#include "btBulletDynamicsCommon.h" + namespace RNR { @@ -28,6 +31,7 @@ namespace RNR private: std::map m_refs; std::stack m_undeserialized; + btDiscreteDynamicsWorld* m_dynamicsWorld; DataModel* m_datamodel; Workspace* m_workspace; RunService* m_runService; @@ -52,6 +56,7 @@ namespace RNR double step(float timestep); void update(); + btDiscreteDynamicsWorld* getDynamicsWorld() { return m_dynamicsWorld; } float getLastDelta() { return m_lastDelta; } DataModel* getDatamodel() { return m_datamodel; } void setInputManager(IInputManager* inputManager) { m_inputManager = inputManager; } diff --git a/Projects/Engine/Source/App/GUI/TopMenuBar.cpp b/Projects/Engine/Source/App/GUI/TopMenuBar.cpp index 59164a8..3457d27 100644 --- a/Projects/Engine/Source/App/GUI/TopMenuBar.cpp +++ b/Projects/Engine/Source/App/GUI/TopMenuBar.cpp @@ -115,6 +115,7 @@ namespace RNR void TopMenuBar::frame() { Workspace* workspace = m_world->getWorkspace(); + btDiscreteDynamicsWorld* dynamicsWorld = m_world->getDynamicsWorld(); char debugtext[512]; char render_debugtext[255]; @@ -127,7 +128,11 @@ namespace RNR snprintf(render_debugtext, 255, "using BATCH_STATIC_GEOMETRY\nGeom Regions: %i", workspace->m_geom->getRegions().size()); break; } - snprintf(debugtext, 512, "Render\nLast DT = %f\n%s\n",m_world->getLastDelta(),render_debugtext,m_world->getOgreSceneManager()); + snprintf(debugtext, 512, "Render\nLast DT = %f\n%s\n\nPhysics\n%i objects, %i constraints", + m_world->getLastDelta(), + render_debugtext, + m_world->getOgreSceneManager(), + dynamicsWorld->getNumCollisionObjects(), dynamicsWorld->getNumConstraints()); m_debugText->setCaption(debugtext); Players* players = (Players*)m_world->getDatamodel()->getService("Players"); diff --git a/Projects/Engine/Source/App/Humanoid/Humanoid.cpp b/Projects/Engine/Source/App/Humanoid/Humanoid.cpp index 02c8cc5..e708461 100644 --- a/Projects/Engine/Source/App/Humanoid/Humanoid.cpp +++ b/Projects/Engine/Source/App/Humanoid/Humanoid.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace RNR { @@ -45,13 +46,38 @@ namespace RNR } if(getNode()) world->getOgreSceneManager()->destroySceneNode(getNode()); + setNode(world->getOgreSceneManager()->getRootSceneNode()->createChildSceneNode()); Ogre::BillboardSet* healthBarSet = world->getOgreSceneManager()->createBillboardSet("HumanoidHealth" + getParent()->getName()); - Ogre::Billboard* healthBarBillboard = healthBarSet->createBillboard(Ogre::Vector3(100, 0, 200)); - getNode()->attachObject(healthBarSet); - getNode()->setPosition(getHead()->getPosition()); + healthBarSet->setBillboardType(Ogre::BillboardType::BBT_PERPENDICULAR_COMMON); + healthBarSet->setDebugDisplayEnabled(true); + //Ogre::Billboard* healthBarBillboard = healthBarSet->createBillboard(Ogre::Vector3(0, 2, 0), Ogre::ColourValue(1, 0, 0, 1)); + Ogre::Billboard* healthBarBillboardFilled = healthBarSet->createBillboard(Ogre::Vector3(0, 2, 0), Ogre::ColourValue(0, 1, 0, 1)); + float healthBarScale = 0.5f; + //healthBarBillboard->setDimensions(5 * healthBarScale, 1 * healthBarScale); + healthBarBillboardFilled->setDimensions((m_health / m_maxHealth) * 5.f * healthBarScale, 1 * healthBarScale); + healthBarSet->setCastShadows(true); - printf("Humanoid::createHealthBar: WIP"); + Ogre::BillboardSet* nameBarSet = world->getOgreSceneManager()->createBillboardSet("HumanoidName" + getParent()->getName()); + nameBarSet->setDebugDisplayEnabled(true); + Ogre::FontPtr comic = Ogre::FontManager::getSingletonPtr()->getByName("ComicSans"); + if(!comic) + printf("Humanoid::createHealthBar: comic == NULL\n"); + comic->putText(nameBarSet, getParent()->getName(), 1); + int num_billboards = nameBarSet->getNumBillboards(); + for(int i = 0; i < num_billboards; i++) + { + Ogre::Billboard* chara = nameBarSet->getBillboard(i); + Ogre::Vector3 chara_pos = chara->getPosition(); + chara_pos.y += 2 + healthBarScale; + chara->setPosition(chara_pos); + } + getNode()->attachObject(healthBarSet); + getNode()->attachObject(nameBarSet); + getNode()->setPosition(getHead()->getPosition()); + getNode()->setOrientation(getHead()->getRotation()); + + printf("Humanoid::createHealthBar: WIP\n"); } void Humanoid::deserializeProperty(char* prop_name, pugi::xml_node prop) @@ -66,6 +92,58 @@ namespace RNR } } + void Humanoid::inputFrame(float dx, float dy) + { + IInputManager* inputManager = world->getInputManager(); + PartInstance* torso = getTorso(); + Camera* camera = world->getWorkspace()->getCurrentCamera(); + if(!torso) + { + printf("Humanoid::inputFrame: no torso\n"); + return; + } + if(inputManager) + { + Ogre::Matrix3 camera_rotation; + camera_rotation.FromEulerAnglesYXZ(camera->getYaw(), Ogre::Radian(0), Ogre::Radian(0)); // we only want yaw because otherwise the movement target will go through the ground/in the air + Ogre::Quaternion direction = torso->getRotation(); + Ogre::Quaternion new_direction = Ogre::Quaternion::nlerp(0.5f, direction, camera_rotation); + float forward = 0; + + if(inputManager->isKeyDown('W')) + forward = 16; + + Ogre::Vector3 move = Ogre::Vector3(0, 0, -forward); + move = direction * move; + move *= world->getLastDelta(); + + bool move_valid = true; + + // TODO: collision checking + + if(!move_valid) + return; + + for(auto& child : *getParent()->getChildren()) + { + PartInstance* bp = dynamic_cast(child); + if(bp) + { + Ogre::Vector3 bp_p = bp->getPosition(); + bp_p += move; + bp->getCFrame().setPosition(bp_p); + Ogre::Matrix3 new_rotation_matrix; + new_direction.ToRotationMatrix(new_rotation_matrix); + bp->getCFrame().setRotation(new_rotation_matrix); + bp->updateMatrix(); + world->getWorkspace()->setDirty(); + } + } + + camera->getCFrame().setPosition(camera->getCFrame().getPosition() + move); + } + } + void Humanoid::addProperties(std::vector& properties) { ReflectionProperty _properties[] = { diff --git a/Projects/Engine/Source/App/InputManager.cpp b/Projects/Engine/Source/App/InputManager.cpp index 383e192..28c6b1b 100644 --- a/Projects/Engine/Source/App/InputManager.cpp +++ b/Projects/Engine/Source/App/InputManager.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace RNR @@ -9,8 +10,7 @@ namespace RNR state.mouse_primary = false; state.mouse_secondary = false; state.mouse_middle = false; - state.mouse_scroll = 0; - } + state.mouse_scroll = 0; } void IInputManager::keyDown(int scancode) { @@ -43,14 +43,14 @@ namespace RNR { if(m_world) { - float xd = x * m_world->getLastDelta(); - float yd = y * m_world->getLastDelta(); + m_mouseDX = x * m_world->getLastDelta(); + m_mouseDY = y * m_world->getLastDelta(); Workspace* workspace = m_world->getWorkspace(); Camera* camera = workspace->getCurrentCamera(); if(camera && state.mouse_secondary) { - camera->cameraFrame(xd, yd); + camera->cameraFrame(m_mouseDX, m_mouseDY); resetMouse(); } @@ -59,10 +59,29 @@ namespace RNR void IInputManager::frame() { - Workspace* workspace = m_world->getWorkspace(); - Camera* camera = workspace->getCurrentCamera(); - if(camera) - camera->cameraFrame(0, 0, false); // update camera position + Players* players = (Players*)m_world->getDatamodel()->getService("Players"); + Player* localPlayer = players->getLocalPlayer(); + if(localPlayer) + { + ModelInstance* character = localPlayer->getCharacter(); + if(character) + { + Humanoid* humanoid = (Humanoid*)character->findFirstChildOfType("Humanoid"); + if(humanoid) + { + humanoid->inputFrame(m_mouseDX, m_mouseDY); + } + } + } + else + { + Workspace* workspace = m_world->getWorkspace(); + Camera* camera = workspace->getCurrentCamera(); + if(camera) + camera->cameraFrame(0, 0, false); // update camera position + } + m_mouseDX = 0; + m_mouseDY = 0; } void IInputManager::mousePrimaryState(bool down) diff --git a/Projects/Engine/Source/App/V8/DataModel/Camera.cpp b/Projects/Engine/Source/App/V8/DataModel/Camera.cpp index 31b9d52..cdfb08b 100644 --- a/Projects/Engine/Source/App/V8/DataModel/Camera.cpp +++ b/Projects/Engine/Source/App/V8/DataModel/Camera.cpp @@ -41,6 +41,7 @@ namespace RNR pitch = old_pitch + pitch; yaw = old_yaw - yaw; + m_yaw = yaw; Ogre::Matrix3 rotation; rotation.FromEulerAnglesYXZ(yaw, pitch, Ogre::Radian(0)); diff --git a/Projects/Engine/Source/App/V8/DataModel/Workspace.cpp b/Projects/Engine/Source/App/V8/DataModel/Workspace.cpp index 19711b2..1ad8055 100644 --- a/Projects/Engine/Source/App/V8/DataModel/Workspace.cpp +++ b/Projects/Engine/Source/App/V8/DataModel/Workspace.cpp @@ -8,7 +8,7 @@ namespace RNR Workspace::Workspace() : ModelInstance() { setName("Workspace"); - m_batchMode = BATCH_STATIC_GEOMETRY; + m_batchMode = BATCH_DONT; m_worldspawn = world->getOgreSceneManager()->getRootSceneNode()->createChildSceneNode(); diff --git a/Projects/Engine/Source/App/V8/Tree/Instance.cpp b/Projects/Engine/Source/App/V8/Tree/Instance.cpp index e96befd..49e90b0 100644 --- a/Projects/Engine/Source/App/V8/Tree/Instance.cpp +++ b/Projects/Engine/Source/App/V8/Tree/Instance.cpp @@ -15,6 +15,17 @@ namespace RNR Instance::~Instance() { setParent(NULL); + if(getNode()) + { + getNode()->removeAndDestroyAllChildren(); + delete getNode(); + } + if(getObject()) + { + if(getObject()->getParentNode() != 0) + getObject()->detachFromParent(); + delete getObject(); + } } std::vector Instance::getProperties() diff --git a/Projects/Engine/Source/App/V8/Tree/PVInstance.cpp b/Projects/Engine/Source/App/V8/Tree/PVInstance.cpp index f9502f2..c006885 100644 --- a/Projects/Engine/Source/App/V8/Tree/PVInstance.cpp +++ b/Projects/Engine/Source/App/V8/Tree/PVInstance.cpp @@ -6,7 +6,7 @@ namespace RNR { PVInstance::PVInstance() : m_cframe(), Instance() { - + } void PVInstance::deserializeProperty(char* prop_name, pugi::xml_node node) @@ -28,4 +28,9 @@ namespace RNR properties.insert(properties.end(), _properties, _properties+(sizeof(_properties)/sizeof(ReflectionProperty))); } + + Ogre::Vector3 PVInstance::relativePositionTo(PVInstance* point) + { + return point->getPosition() - getPosition(); + } } \ No newline at end of file diff --git a/Projects/Engine/Source/App/V8/World/World.cpp b/Projects/Engine/Source/App/V8/World/World.cpp index a6332c2..85b7ee3 100644 --- a/Projects/Engine/Source/App/V8/World/World.cpp +++ b/Projects/Engine/Source/App/V8/World/World.cpp @@ -15,6 +15,13 @@ namespace RNR { Instance::setWorld(this); + btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(); + btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); + btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase(); + btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver; + m_dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration); + m_dynamicsWorld->setGravity(btVector3(0, -10, 0)); + m_inputManager = 0; m_instanceFactory = new InstanceFactory(); @@ -112,7 +119,7 @@ namespace RNR m_undeserialized.pop(); s.instance->setParent(s.parent); - + pugi::xml_node props = s.node.child("Properties"); for(pugi::xml_node prop : props.children()) { @@ -143,7 +150,10 @@ namespace RNR double World::step(float timestep) { if(m_runService && m_runService->getRunning()) + { m_runService->step(timestep); + m_dynamicsWorld->stepSimulation(timestep); + } m_lastDelta = timestep; return 0.0; } diff --git a/Projects/Engine/Source/Network/Player.cpp b/Projects/Engine/Source/Network/Player.cpp index e7cb5f8..1a01c5c 100644 --- a/Projects/Engine/Source/Network/Player.cpp +++ b/Projects/Engine/Source/Network/Player.cpp @@ -37,8 +37,19 @@ namespace RNR PartInstance* head = new PartInstance(); head->setName("Head"); head->setSize(Ogre::Vector3(2, 1, 1)); + head->setBrickColor(24); + head->getCFrame().setPosition(Ogre::Vector3(0, 1.5, 0)); + head->updateMatrix(); head->setParent(m_character); + PartInstance* torso = new PartInstance(); + torso->setName("Torso"); + torso->setSize(Ogre::Vector3(2, 2, 1)); + torso->setBrickColor(23); + torso->getCFrame().setPosition(Ogre::Vector3(0, 0, 0)); + torso->updateMatrix(); + torso->setParent(m_character); + Humanoid* character_humanoid = new Humanoid(); character_humanoid->setParent(m_character); m_character->setParent(world->getWorkspace()); diff --git a/Projects/Engine/Source/Network/Players.cpp b/Projects/Engine/Source/Network/Players.cpp index 10e2cda..ec7bf77 100644 --- a/Projects/Engine/Source/Network/Players.cpp +++ b/Projects/Engine/Source/Network/Players.cpp @@ -12,8 +12,8 @@ namespace RNR { if(m_localPlayer) { - printf("Players::createLocalPlayer: attempt to create another local player\n", userId); - return m_localPlayer; + printf("Players::createLocalPlayer: attempt to create another local player %i\n", userId); + return 0; } printf("Players::createLocalPlayer: created player %i\n", userId); m_localPlayer = new Player();