commit 7e5c96c123cdbfe2c1a1df238fe3c2a5e873888d Author: Vectozavr <60608292+vectozavr@users.noreply.github.com> Date: Mon Sep 13 19:53:43 2021 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db66441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Project exclude paths +/cmake-build-debug/ +/cmake-build-release/ \ No newline at end of file diff --git a/Ak47.cpp b/Ak47.cpp new file mode 100755 index 0000000..982d9dd --- /dev/null +++ b/Ak47.cpp @@ -0,0 +1,17 @@ +// +// Created by Иван Ильин on 02.06.2021. +// + +#include +#include "Ak47.h" + +using namespace std; + +Ak47::Ak47(int ammo, const std::string& weaponName) : Weapon(weaponName, "../obj/ak47.obj", "../obj/ak47_mat.txt", Point4D{3, 3, 3}, Point4D{-0.8, 1.3, 0.3}, Point4D{0, M_PI, 0}) { + fireSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/ak47.ogg")); + reloadSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/reload_ak47.ogg")); + + + _stockAmmo = ammo - _clipCapacity; + _fireDelay = 0.1; +} diff --git a/Ak47.h b/Ak47.h new file mode 100755 index 0000000..a9274b4 --- /dev/null +++ b/Ak47.h @@ -0,0 +1,16 @@ +// +// Created by Иван Ильин on 02.06.2021. +// + +#ifndef SHOOTER_AK47_H +#define SHOOTER_AK47_H + +#include "Weapon.h" + +class Ak47 : public Weapon { +public: + explicit Ak47(int ammo = 100, const std::string& weaponName = "ak47"); +}; + + +#endif //SHOOTER_3DZAVR_AK47_H diff --git a/Bonus.cpp b/Bonus.cpp new file mode 100755 index 0000000..91685e4 --- /dev/null +++ b/Bonus.cpp @@ -0,0 +1,13 @@ +// +// Created by Иван Ильин on 05.06.2021. +// + +#include "Bonus.h" + +Bonus::Bonus(const std::string &bonusName, const std::string &filename, const std::string &materials, const Point4D &scale) { + _name = bonusName; + loadObj(filename, materials, scale); + setCollider(false); + + a_rotate("a_rotation", Point4D{0, 2*M_PI, 0}, 4, Animation::Continue, Animation::linear); +} diff --git a/Bonus.h b/Bonus.h new file mode 100755 index 0000000..27aafc0 --- /dev/null +++ b/Bonus.h @@ -0,0 +1,21 @@ +// +// Created by Иван Ильин on 05.06.2021. +// + +#ifndef SHOOTER_BONUS_H +#define SHOOTER_BONUS_H + +#include "World.h" +#include "Player.h" + +class Bonus : public Mesh { +protected: + std::string _name; +public: + explicit Bonus(const std::string &bonusName, const std::string& filename, const std::string &materials = "", const Point4D& scale = Point4D{1, 1, 1}); + + [[nodiscard]] std::string name() const { return _name; } +}; + + +#endif //SHOOTER_3DZAVR_BONUS_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..c478e53 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,105 @@ +cmake_minimum_required(VERSION 3.17) +project(shooter) + +set(CMAKE_CXX_STANDARD 20) + +include_directories(engine) + +add_executable(shooter + # game: + main.cpp + Player.cpp + Player.h + Client.cpp + Client.h + Server.cpp + Server.h + Weapon.cpp + Weapon.h + Ak47.cpp + Ak47.h + Shotgun.cpp + Shotgun.h + Gun.cpp + Gun.h + Bonus.cpp + Bonus.h + Gold_Ak47.h + Rifle.cpp + Rifle.h + # 3d engine: + engine/utils/Time.h + engine/utils/Time.cpp + engine/utils/Point4D.h + engine/utils/Point4D.cpp + engine/utils/Matrix4x4.h + engine/utils/Matrix4x4.cpp + engine/Triangle.h + engine/Triangle.cpp + engine/Mesh.h + engine/Mesh.cpp + engine/utils/Log.h + engine/utils/Log.cpp + engine/ResourceManager.h + engine/ResourceManager.cpp + engine/World.h + engine/World.cpp + engine/Camera.h + engine/Camera.cpp + engine/Screen.h + engine/Screen.cpp + engine/Engine.h + engine/Engine.cpp + engine/Plane.h + engine/Plane.cpp + engine/CameraController.h + engine/CameraController.cpp + engine/animation/Animatable.h + engine/animation/Animation.h + engine/animation/Interpolation.h + engine/animation/Animatable.cpp + engine/animation/Animation.cpp + engine/animation/ATranslate.h + engine/animation/AScale.h + engine/animation/ARotate.h + engine/animation/AWait.h + engine/physics/RigidBody.cpp + engine/physics/RigidBody.h + engine/physics/Simplex.h + engine/physics/Solver.cpp + engine/physics/Solver.h + engine/Object.h + engine/gui/Button.cpp + engine/gui/Button.h + engine/gui/Window.cpp + engine/gui/Window.h + engine/network/ClientUDP.cpp + engine/network/ClientUDP.h + engine/network/MsgType.cpp + engine/network/MsgType.h + engine/network/ReliableMsg.cpp + engine/network/ReliableMsg.h + engine/network/ServerUDP.cpp + engine/network/ServerUDP.h + engine/network/UDPConnection.cpp + engine/network/UDPConnection.h + engine/network/UDPSocket.cpp + engine/network/UDPSocket.h + engine/network/config.h + engine/animation/AFunction.h + ) + +if(${APPLE}) + include_directories(/usr/local/include) +else() + set(SFML_DIR "C:/Libraries/SFML/lib/cmake/SFML") + set(SFML_STATIC_LIBRARIES TRUE) +endif() + +find_package(SFML 2.5.1 COMPONENTS graphics audio REQUIRED) + +if (SFML_FOUND) + include_directories(${SFML_INCLUDE_DIR}) +endif() + +target_link_libraries(shooter sfml-audio sfml-network sfml-graphics sfml-window sfml-system) \ No newline at end of file diff --git a/Client.cpp b/Client.cpp new file mode 100755 index 0000000..132822c --- /dev/null +++ b/Client.cpp @@ -0,0 +1,217 @@ +// +// Created by Иван Ильин on 25.05.2021. +// + +#include "Client.h" +#include "utils/Log.h" +#include "Bonus.h" + +void Client::updatePacket() { + sf::Packet packet; + packet << MsgType::ClientUpdate << _player->position().x() << _player->position().y() << _player->position().z() << _player->angle().y() << _player->camera()->angleLeftUpLookAt().x(); + _socket.send(packet, _socket.serverId()); +} + +void Client::processInit(sf::Packet& packet) { + sf::Uint16 targetId; + double buf[4]; + + while (packet >> targetId >> buf[0] >> buf[1] >> buf[2] >> buf[3]) + { + if(targetId != _socket.ownId()) { + std::string name = "Player_" + std::to_string(targetId); + _players.insert({ targetId, std::make_shared() }); + (*_world).addMesh(_players[targetId], name); + _players[targetId]->setVisible(true); + _players[targetId]->setAcceleration(Point4D{0, 0, 0}); + + // add head and other stuff: + _world->loadObj(name + "_head", "../obj/cube.obj", "",Point4D{0.7, 0.7, 0.7}); + (*_world)[name + "_head"]->translate(Point4D{0, 2, 0}); + (*_world)[name + "_head"]->setCollider(false); + _players[targetId]->attach((*_world)[name + "_head"]); + + _world->loadObj(name + "_eye1", "../obj/cube.obj", "",Point4D{0.2, 0.2, 0.05}); + (*_world)[name + "_eye1"]->translate(Point4D{0.3, 2.1, 0.7}); + (*_world)[name + "_eye1"]->setCollider(false); + (*_world)[name + "_eye1"]->setColor({147, 159, 255}); + (*_world)[name + "_head"]->attach((*_world)[name + "_eye1"]); + + _world->loadObj(name + "_eye2", "../obj/cube.obj", "",Point4D{0.2, 0.2, 0.05}); + (*_world)[name + "_eye2"]->translate(Point4D{-0.3, 2.1, 0.7}); + (*_world)[name + "_eye2"]->setCollider(false); + (*_world)[name + "_eye2"]->setColor({147, 159, 255}); + (*_world)[name + "_head"]->attach((*_world)[name + "_eye2"]); + + _players[targetId]->translateToPoint(Point4D{ buf[0], buf[1], buf[2]}); + _players[targetId]->setHealth(buf[3]); + } + } +} + +void Client::processUpdate(sf::Packet& packet) { + sf::Uint16 targetId; + double buf[6]; + + while (packet >> targetId >> buf[0] >> buf[1] >> buf[2] >> buf[3] >> buf[4] >> buf[5]) { + if (_players.count(targetId)) { + std::string name = "Player_" + std::to_string(targetId); + _players[targetId]->translateToPoint(Point4D{buf[0], buf[1], buf[2]}); + _players[targetId]->setHealth(buf[3]); + _players[targetId]->rotateToAngle(Point4D{0, buf[4], 0}); + //(*_world)[name + "_head"]->rotateToAngle({buf[5], 2*buf[4], 0}); + (*_world)[name + "_head"]->rotate(Matrix4x4::RotationY(buf[4]) * Point4D{1, 0, 0}, + buf[5] - _players[targetId]->headAngle()); + _players[targetId]->setHeadAngle(buf[5]); + } else if (targetId == _socket.ownId()) { + _player->setHealth(buf[3]); + } + } +} + +void Client::processNewClient(sf::Packet& packet) { + sf::Uint16 targetId; + + packet >> targetId; + + std::string name = "Player_" + std::to_string(targetId); + _players.insert({ targetId, std::make_shared() }); + _world->addMesh(_players[targetId], name); + _players[targetId]->setVisible(true); + _players[targetId]->setAcceleration(Point4D{0, 0, 0}); + + // add head and other stuff: + _world->loadObj(name + "_head", "../obj/cube.obj","",Point4D{0.7, 0.7, 0.7}); + (*_world)[name + "_head"]->translate(Point4D{0, 2, 0}); + (*_world)[name + "_head"]->setCollider(false); + _players[targetId]->attach((*_world)[name + "_head"]); + + _world->loadObj(name + "_eye1", "../obj/cube.obj","",Point4D{0.2, 0.2, 0.05}); + (*_world)[name + "_eye1"]->translate(Point4D{0.3, 2.1, 0.7}); + (*_world)[name + "_eye1"]->setCollider(false); + (*_world)[name + "_eye1"]->setColor({147, 159, 255}); + (*_world)[name + "_head"]->attach((*_world)[name + "_eye1"]); + + _world->loadObj(name + "_eye2", "../obj/cube.obj", "",Point4D{0.2, 0.2, 0.05}); + (*_world)[name + "_eye2"]->translate(Point4D{-0.3, 2.1, 0.7}); + (*_world)[name + "_eye2"]->setCollider(false); + (*_world)[name + "_eye2"]->setColor({147, 159, 255}); + (*_world)[name + "_head"]->attach((*_world)[name + "_eye2"]); +} + +void Client::processDisconnect(sf::Uint16 targetId) { + if (targetId != _socket.ownId() && _players.count(targetId)) { + std::string name = "Player_" + std::to_string(targetId); + _world->removeMesh(name); + _world->removeMesh(name + "_head"); + _world->removeMesh(name + "_eye1"); + _world->removeMesh(name + "_eye2"); + + _players.erase(targetId); + } +} + + +void Client::processCustomPacket(MsgType type, sf::Packet& packet) { + int buff[3]; + sf::Uint16 buffId[2]; + double dbuff[10]; + std::string tmp, tmp2; + Point4D p1, p2; + + switch (type) { + case MsgType::Kill: + packet >> buffId[0] >> buffId[1]; + if(buffId[0] == _socket.ownId()) { + _player->addDeath(); + // respawn + _player->translateToPoint(Point4D{50.0*(-1 + 2.0*(double)rand()/RAND_MAX),30.0*(double)rand()/RAND_MAX,50.0*(-1 + 2.0*(double)rand()/RAND_MAX)}); + _player->playDeath(); + _player->initWeapons(); + _player->setFullAbility(); + } + else + _players[buffId[0]]->addDeath(); + if(buffId[1] == _socket.ownId()) { + _player->addKill(); + _player->playKill(); + } + else + _players[buffId[1]]->addKill(); + break; + case MsgType::FireTrace: + packet >> dbuff[0] >> dbuff[1] >> dbuff[2] >> dbuff[3] >> dbuff[4] >> dbuff[5]; + + p1 = Point4D(dbuff[0], dbuff[1], dbuff[2]); + p2 = Point4D(dbuff[3], dbuff[4], dbuff[5]); + + tmp = "Client_fireTraces_" + std::to_string(fireTraces++); + _world->addMesh(std::make_shared(Mesh::LineTo(p1, p2, 0.05)), tmp); + (*_world)[tmp]->setCollider(false); + + (*_world)[tmp]->a_color(tmp + "_fadeOut", {255, 255, 255, 0}, 1, Animation::None, Animation::linear); + (*_world)["Player_im"]->a_function(tmp + "delete", [this, tmp](){ deleteTrace(_world, tmp); }, 1, 2); + + break; + case MsgType::InitBonuses: + while (packet >> tmp >> dbuff[0] >> dbuff[1] >> dbuff[2]) { + tmp2 = tmp.substr(6, tmp.size()-3-5); + _world->addMesh(std::make_shared(tmp, "../obj/" + tmp2 + ".obj", "../obj/" + tmp2 + "_mat.txt", Point4D{3, 3, 3}), tmp); + (*_world)[tmp]->translateToPoint(Point4D(dbuff[0], dbuff[1], dbuff[2])); + } + break; + + case MsgType::AddBonus: + packet >> tmp >> dbuff[0] >> dbuff[1] >> dbuff[2]; + + tmp2 = tmp.substr(6, tmp.size()-3-5); + _world->addMesh(std::make_shared(tmp, "../obj/" + tmp2 + ".obj", "../obj/" + tmp2 + "_mat.txt", Point4D{3, 3, 3}), tmp); + (*_world)[tmp]->translateToPoint(Point4D(dbuff[0], dbuff[1], dbuff[2])); + + break; + case MsgType::RemoveBonus: + packet >> tmp; + _world->removeMesh(tmp); + break; + } +} + +void Client::processDisconnected() { + for (auto it = _players.begin(); it != _players.end();) { + std::string name = "Player_" + std::to_string(it->first); + + _world->removeMesh(name); + _world->removeMesh(name + "_head"); + _world->removeMesh(name + "_eye1"); + _world->removeMesh(name + "_eye2"); + _players.erase(it++); + } +} + +void Client::damagePlayer(sf::Uint16 targetId, double damage) { + sf::Packet packet; + + packet << MsgType::Damage << targetId << damage; + _socket.send(packet, _socket.serverId()); + + Log::log("Client: damagePlayer " + std::to_string(targetId) + " ( -" + std::to_string(damage) + "hp )"); +} + +void Client::addTrace(const Point4D& from, const Point4D& to) { + sf::Packet packet; + + packet << MsgType::FireTrace << from.x() << from.y() << from.z() << to.x() << to.y() << to.z(); + _socket.send(packet, _socket.serverId()); +} + +void Client::deleteTrace(const std::shared_ptr &world, const std::string &traceName) { + world->removeMesh(traceName); +} + +void Client::takeBonus(const std::string& bonusName) { + sf::Packet packet; + + packet << MsgType::RemoveBonus << bonusName; + _socket.send(packet, _socket.serverId()); +} + diff --git a/Client.h b/Client.h new file mode 100755 index 0000000..79ebb77 --- /dev/null +++ b/Client.h @@ -0,0 +1,45 @@ +// +// Created by Иван Ильин on 25.05.2021. +// + +#ifndef SHOOTER_CLIENT_H +#define SHOOTER_CLIENT_H + +#include + +#include "network/ClientUDP.h" +#include "Player.h" + +class Client : public ClientUDP { +private: + std::shared_ptr _player; + std::shared_ptr _world; + + std::map> _players{}; + + int fireTraces = 0; +public: + Client(std::shared_ptr player, std::shared_ptr world) : _player(std::move(player)), _world(std::move(world)) {}; + + void updatePacket() override; + + void processInit(sf::Packet& packet) override; + void processUpdate(sf::Packet& packet) override; + void processNewClient(sf::Packet& packet) override; + void processDisconnect(sf::Uint16 targetId) override; + + void processCustomPacket(MsgType type, sf::Packet& packet) override; + + void processDisconnected() override; + + void damagePlayer(sf::Uint16 targetId, double damage); + + void takeBonus(const std::string& bonusName); + + void addTrace(const Point4D& from, const Point4D& to); + + void deleteTrace(const std::shared_ptr &world, const std::string& traceName); +}; + + +#endif //MINECRAFT_3DZAVR_CLIENT_H diff --git a/Gold_Ak47.h b/Gold_Ak47.h new file mode 100755 index 0000000..971d0ad --- /dev/null +++ b/Gold_Ak47.h @@ -0,0 +1,27 @@ +// +// Created by Иван Ильин on 05.06.2021. +// + +#ifndef SHOOTER_GOLD_AK47_H +#define SHOOTER_GOLD_AK47_H + +#include "Weapon.h" + +class Gold_Ak47 : public Weapon { +public: + explicit Gold_Ak47(int ammo = 200, const std::string& weaponName = "gold_ak47") : Weapon(weaponName, "../obj/ak47.obj", "../obj/gold_ak47_mat.txt", Point4D{3, 3, 3}, Point4D{-0.8, 1.3, 0.3}, Point4D{0, M_PI, 0}) { + fireSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/ak47.ogg")); + reloadSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/reload_ak47.ogg")); + + _initialPack = 200; + _spreading = 1.0; + _reloadTime = 1.5; + _clipCapacity = 60; + _stockAmmo = ammo - _clipCapacity; + _fireDelay = 0.05; + _damage = 600; + _clipAmmo = _clipCapacity; + } +}; + +#endif //SHOOTER_3DZAVR_GOLD_AK47_H diff --git a/Gun.cpp b/Gun.cpp new file mode 100755 index 0000000..4d31298 --- /dev/null +++ b/Gun.cpp @@ -0,0 +1,22 @@ +// +// Created by Иван Ильин on 03.06.2021. +// + +#include +#include "Gun.h" + +using namespace std; + +Gun::Gun(int ammo, const std::string& weaponName) : Weapon(weaponName, "../obj/gun.obj", "../obj/gun_mat.txt", Point4D{3, 3, 3}, Point4D{-0.8, 1.3, 0.3}, Point4D{0, M_PI, 0}) { + fireSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/gun.ogg")); + reloadSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/reload_gun.ogg")); + + _initialPack = 30; + _clipCapacity = 6; // how much ammo can be stored in one clip + _stockAmmo = ammo - _clipCapacity; // how much ammo do you have in stock + _clipAmmo = _clipCapacity; // how much ammo do you have in current clip + _reloadTime = 2; + _fireDelay = 0.3; // time delay between fires + _damage = 800; + _spreading = 3.0; +} diff --git a/Gun.h b/Gun.h new file mode 100755 index 0000000..db5a25a --- /dev/null +++ b/Gun.h @@ -0,0 +1,16 @@ +// +// Created by Иван Ильин on 03.06.2021. +// + +#ifndef SHOOTER_GUN_H +#define SHOOTER_GUN_H + +#include "Weapon.h" + +class Gun : public Weapon { +public: + explicit Gun(int ammo = 30, const std::string& weaponName = "gun"); +}; + + +#endif //SHOOTER_3DZAVR_GUN_H diff --git a/Player.cpp b/Player.cpp new file mode 100755 index 0000000..46b8b3a --- /dev/null +++ b/Player.cpp @@ -0,0 +1,343 @@ +// +// Created by Иван Ильин on 14.03.2021. +// + +#include "Player.h" +#include "Screen.h" +#include "ResourceManager.h" +#include "utils/Log.h" + +void Player::update() { + + if(inCollision()) + p_velocity -= p_velocity*Time::deltaTime()*2; + + if(isInSlowMo) { + if(_ability > 0) + _ability -= Time::deltaTime(); + else { + _ability = 0; + isInSlowMo = false; + setVelocity(velocity()*slowMoCoefficient); + setAcceleration(p_acceleration*slowMoCoefficient*slowMoCoefficient); + slowMoSound.stop(); + unSlowMoSound.play(); + } + } + + double coeff = isInSlowMo ? 1.0/slowMoCoefficient : 1.0; + + bool inRunning_old = inRunning; + inRunning = _screen != nullptr && (Screen::isKeyPressed(sf::Keyboard::A) || Screen::isKeyPressed(sf::Keyboard::D) ||Screen::isKeyPressed(sf::Keyboard::W) || Screen::isKeyPressed(sf::Keyboard::S)); + + // in case when the camera is attached we make some animation during running + if(_camera != nullptr && inRunning && !_camera->isInAnim()) { + _camera->a_translate("hor_oscil", -_camera->left()/12, 0.3/coeff, Animation::LoopOut::None, Animation::cos); + _camera->a_wait("hor_oscil", 0); + _camera->a_translate("hor_oscil", _camera->left()/12, 0.3/coeff, Animation::LoopOut::None, Animation::cos); + + _camera->a_translate("vert_oscil", -Point4D{0, 1, 0}/24, 0.15/coeff, Animation::LoopOut::None, Animation::cos); + _camera->a_wait("vert_oscil", 0); + _camera->a_translate("vert_oscil", Point4D{0, 1, 0}/24, 0.15/coeff, Animation::LoopOut::None, Animation::cos); + _camera->a_wait("vert_oscil", 0); + _camera->a_translate("vert_oscil", -Point4D{0, 1, 0}/24, 0.15/coeff, Animation::LoopOut::None, Animation::cos); + _camera->a_wait("vert_oscil", 0); + _camera->a_translate("vert_oscil", Point4D{0, 1, 0}/24, 0.15/coeff, Animation::LoopOut::None, Animation::cos); + + _camera->a_translateToPoint("init", position() + Point4D{0, 1.8, 0}, 0.3/coeff, Animation::None, Animation::cos); + + } else if(_camera != nullptr && inRunning_old && !inRunning) { + _camera->a_stopAllAnimations(); + + _camera->a_translateToPoint("init", position() + Point4D{0, 1.8, 0}, 0.15/coeff, Animation::None, Animation::cos); + } + + auto rayToFloor = (*_world).rayCast(position(), position() + Point4D{0, -5, 0}); + + if(_world != nullptr && _screen != nullptr && _camera != nullptr) { + // Left and right + if (Screen::isKeyPressed(sf::Keyboard::A)) { + translate(_camera->left() * Time::deltaTime() * walkSpeed * coeff); + if(inCollision()) + setVelocity(Point4D{0,0,0}); + } + if (Screen::isKeyPressed(sf::Keyboard::D)) { + translate(-_camera->left() * Time::deltaTime() * walkSpeed * coeff); + if(inCollision()) + setVelocity(Point4D{0,0,0}); + } + + // Forward and backward + if (Screen::isKeyPressed(sf::Keyboard::W)) { + translate(_camera->left().cross3D(Point4D{0, 1, 0}) * Time::deltaTime() * walkSpeed * coeff); + if(inCollision()) + setVelocity(Point4D{0,0,0}); + } + + if (Screen::isKeyPressed(sf::Keyboard::S)) { + translate(-_camera->left().cross3D(Point4D{0, 1, 0}) * Time::deltaTime() * walkSpeed * coeff); + + if(inCollision()) + setVelocity(Point4D{0,0,0}); + } + + if (_ability > 0 && !isInSlowMo && Screen::isKeyPressed(sf::Keyboard::LShift)) { + // slow mo + isInSlowMo = true; + setVelocity(velocity()/slowMoCoefficient); + setAcceleration(p_acceleration/(slowMoCoefficient*slowMoCoefficient)); + unSlowMoSound.stop(); + slowMoSound.play(); + } else if (isInSlowMo && !Screen::isKeyPressed(sf::Keyboard::LShift)) { + isInSlowMo = false; + setVelocity(velocity()*slowMoCoefficient); + setAcceleration(p_acceleration*slowMoCoefficient*slowMoCoefficient); + slowMoSound.stop(); + unSlowMoSound.play(); + } + + if (Screen::isKeyPressed(sf::Keyboard::Space) && inCollision()) { + addVelocity(Point4D{0, std::abs(_collisionNormal.y())*sqrt(2 * g * jumpHeight)*coeff, 0}); + translate(Point4D{0, Time::deltaTime() * walkSpeed * 2 * coeff, 0}); + } + + // Mouse movement + Point4D disp = _screen->getMouseDisplacement(); + + rotate(Point4D{0, -disp.x() / 1000.0, 0}); + p_velocity = Matrix4x4::RotationY(-disp.x() / 1000.0)*p_velocity; + + double rotationLeft = disp.y() / 1000.0; + + // You can only see in range [-90 : 90] grad + if (_camera->angleLeftUpLookAt().x() + rotationLeft > M_PI / 2) + rotationLeft = M_PI / 2 - _camera->angleLeftUpLookAt().x(); + if (_camera->angleLeftUpLookAt().x() + rotationLeft < -M_PI / 2) + rotationLeft = -M_PI / 2 - _camera->angleLeftUpLookAt().x(); + + _camera->rotateLeft(rotationLeft); + rotateWeaponsRelativePoint(position() + Point4D{0, 1.8, 0}, _camera->left(), rotationLeft); + + if (_screen->isKeyTapped(sf::Keyboard::Right) || _screen->isKeyTapped(sf::Keyboard::E)) { + if(_weapons.size() > 1) { + // change '_selectedWeapon' + _weapons[_selectedWeapon]->removeFromWorld(_world); + _selectedWeapon = (_selectedWeapon + 1) % _weapons.size(); + _weapons[_selectedWeapon]->addToWorld(_world); + Log::log("selected _selectedWeapon " + std::to_string(_selectedWeapon)); + changeWeaponSound.play(); + } + } + + if (_screen->isKeyTapped(sf::Keyboard::Left) || _screen->isKeyTapped(sf::Keyboard::Q)) { + if(_weapons.size() > 1) { + // change '_selectedWeapon' + _weapons[_selectedWeapon]->removeFromWorld(_world); + if (_selectedWeapon > 0) + _selectedWeapon = (_selectedWeapon - 1) % _weapons.size(); + else + _selectedWeapon = _weapons.size() - 1; + _weapons[_selectedWeapon]->addToWorld(_world); + Log::log("selected _selectedWeapon " + std::to_string(_selectedWeapon)); + changeWeaponSound.play(); + } + } + + if (_screen->isButtonPressed(sf::Mouse::Button::Left)) { + auto damagedPlayers = _weapons[_selectedWeapon]->fire(_world, _camera); + for(auto& damagedPlayer : damagedPlayers) { + sf::Uint16 targetId = std::stoi(damagedPlayer.first.substr(7)); + damagePlayerCallBack(targetId, damagedPlayer.second); + } + } + + if(Screen::isKeyPressed(sf::Keyboard::R)) { + _weapons[_selectedWeapon]->reload(); + } + + if (inRunning && inCollision() && walkSound.getStatus() != sf::Sound::Status::Playing) { + if ((position() - rayToFloor.first).abs() < 2) { + int soundNum = round((double) rand() / RAND_MAX * 5) + 1; + walkSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/stonestep" + std::to_string(soundNum) + ".ogg")); + //walkSound.setVolume(30); + walkSound.play(); + } + } + } + + oldVelocity = velocity(); +} + +void Player::rotateWeaponsRelativePoint(const Point4D& point4D, const Point4D& v, double val) { + for(auto& weapon : _weapons) + weapon->rotateRelativePoint(point4D, v, val); +} + +void Player::drawStats() { + + if(_screen == nullptr) + return; + + // health bar + int xPos = 10; + int yPos = _screen->height() - 10 - 10; + + int width = _screen->width()/2 - 20; + int height = 10; + + sf::ConvexShape polygon1; + polygon1.setPointCount(4); + sf::ConvexShape polygon2; + polygon2.setPointCount(4); + + polygon1.setPoint(0, sf::Vector2f((float)xPos, (float)yPos)); + polygon1.setPoint(1, sf::Vector2f((float)(xPos + width), (float)yPos)); + polygon1.setPoint(2, sf::Vector2f((float)(xPos + width), (float)(yPos + height))); + polygon1.setPoint(3, sf::Vector2f((float)xPos, (float)(yPos + height))); + + polygon2.setPoint(0, sf::Vector2f((float)xPos, (float)yPos)); + polygon2.setPoint(1, sf::Vector2f((float)xPos + width * _health / _healthMax, (float)yPos)); + polygon2.setPoint(2, sf::Vector2f((float)xPos + width * _health / _healthMax, (float)(yPos + height))); + polygon2.setPoint(3, sf::Vector2f((float)xPos, (float)(yPos + height))); + + polygon1.setFillColor({ 255, 174, 174, 100 }); + polygon2.setFillColor({ static_cast((_healthMax - _health)/_healthMax * 255), static_cast(_health * 255 / _healthMax), 0, 100 }); + + polygon1.setOutlineThickness(3); + _screen->window.draw(polygon2); + + // ability bar + sf::ConvexShape polygon3; + polygon3.setPointCount(4); + polygon3.setPoint(0, sf::Vector2f((float)xPos, (float)yPos - 15)); + polygon3.setPoint(1, sf::Vector2f((float)xPos + width * _ability / _abilityMax, (float)yPos - 15)); + polygon3.setPoint(2, sf::Vector2f((float)xPos + width * _ability / _abilityMax, (float)(yPos - 15 + height))); + polygon3.setPoint(3, sf::Vector2f((float)xPos, (float)(yPos - 15 + height))); + polygon3.setFillColor({ 255, 168, 168, 100 }); + _screen->window.draw(polygon3); + + // ammo + sf::Text text_health; + + text_health.setFont(*ResourceManager::loadFont("../engine/fonts/Roboto-Medium.ttf")); + + // text health + text_health.setCharacterSize(20); + text_health.setFillColor(sf::Color::White); + text_health.setStyle(sf::Text::Italic); + text_health.setPosition(0, 0); + + text_health.setString(std::to_string((int)_health)); + + // text ammo + sf::Text text_ammo1(text_health); + sf::Text text_ammo2(text_health); + + int ammo1Size = 100; + int ammo2Size = 50; + + auto balance = _weapons[_selectedWeapon]->balance(); + + text_ammo1.setCharacterSize(ammo1Size); + text_ammo1.setString(std::to_string((int)balance.first)); + text_ammo1.setPosition(150, _screen->height() - 50 - ammo1Size); + text_ammo1.setFillColor(sf::Color(0, 0, 0, 100)); + _screen->window.draw(text_ammo1); + + text_ammo2.setCharacterSize(ammo2Size); + text_ammo2.setString(std::to_string((int)balance.second)); + text_ammo2.setPosition(50, _screen->height() - 50 - ammo2Size); + text_ammo2.setFillColor(sf::Color(0, 0, 0, 70)); + _screen->window.draw(text_ammo2); + + // text killSound/deathSound stats + sf::Text text_kills(text_health); + text_kills.setStyle(sf::Text::Bold); + text_kills.setString("KILLS: " + std::to_string((int)_kills) + " | " + "DEATHS: " + std::to_string((int)_deaths)); + text_kills.setFillColor(sf::Color(0, 0, 0, 100)); + text_kills.setCharacterSize(ammo2Size/2); + text_kills.setPosition(10, 10); + + _screen->window.draw(text_kills); + + sf::Text text_deaths(text_health); + + text_deaths.setString(std::to_string((int)_deaths)); + text_deaths.setFillColor(sf::Color(100, 0, 0, 100)); + text_deaths.setCharacterSize(ammo2Size); + text_deaths.setPosition(10, ammo2Size + 10); +} + +void Player::playDeath() { + deathSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/classic_hurt.ogg")); + deathSound.play(); +} + +void Player::playKill() { + killSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/kill.ogg")); + killSound.play(); +} + +void Player::collisionWithObject(const std::string &objName, const std::shared_ptr &obj) { + if(objName.find("Bonus_gun") != std::string::npos) + addWeapon(std::make_shared()); + + if(objName.find("Bonus_shotgun") != std::string::npos) + addWeapon(std::make_shared()); + + if(objName.find("Bonus_ak47") != std::string::npos) + addWeapon(std::make_shared()); + + if(objName.find("Bonus_gold_ak47") != std::string::npos) + addWeapon(std::make_shared()); + + if(objName.find("Bonus_rifle") != std::string::npos) + addWeapon(std::make_shared()); + + if(objName.find("Bonus_hill") != std::string::npos) + setFullHealth(); + + if(objName.find("Bonus_ability") != std::string::npos) + setFullAbility(); + + if(objName.find("Bonus") != std::string::npos) { + _world->removeMesh(objName); + takeBonusCallBack(objName); + } +} + +void Player::addWeapon(const std::shared_ptr &weapon) { + changeWeaponSound.play(); + + if(!_weapons.empty()) { + for(auto& w : _weapons) { + if (w->name() == weapon->name()) { + w->addAmmo(w->initialPack()); + return; + } + } + } + + _weapons.push_back(weapon); + _weapons.back()->attachToPlayer(*this); + + _weapons.back()->translate(position()); + _weapons.back()->rotateRelativePoint(position() + Point4D{0, 1.8, 0}, Point4D{0, 1, 0}, p_angle.y()); + _weapons.back()->rotateRelativePoint(position() + Point4D{0, 1.8, 0}, _camera->left(), _camera->angleLeftUpLookAt().x()); + + _weapons.back()->setAddTraceCallBack(addTraceCallBack); + changeWeaponSound.play(); +} + +void Player::initWeapons() { + + if(!_weapons.empty()) { + _weapons[_selectedWeapon]->removeFromWorld(_world); + _weapons.clear(); + } + + _selectedWeapon = 0; + addWeapon(std::make_shared()); + + _weapons[_selectedWeapon]->addToWorld(_world); +} diff --git a/Player.h b/Player.h new file mode 100755 index 0000000..3cdcc73 --- /dev/null +++ b/Player.h @@ -0,0 +1,165 @@ +// +// Created by Иван Ильин on 14.03.2021. +// + +#ifndef SHOOTER_PLAYER_H +#define SHOOTER_PLAYER_H + +#include +#include +#include +#include "Mesh.h" +#include "Camera.h" +#include "World.h" +#include "Ak47.h" +#include "Shotgun.h" +#include "Gun.h" +#include "Gold_Ak47.h" +#include "Rifle.h" + +class Player : public Mesh{ +private: + double _healthMax = 100; + double _health = _healthMax; + + double _abilityMax = 10; + double _ability = _abilityMax; + + double jumpHeight = 3; + double walkSpeed = 10; + double _headAngle = 0; + + unsigned _kills = 0; + unsigned _deaths = 0; + + double g = 35; + + double slowMoCoefficient = 5; + bool isInSlowMo = false; + + std::shared_ptr _camera; + std::shared_ptr _screen; + + std::shared_ptr _world; + + bool inRunning = false; + + // sounds + sf::Sound killSound; + sf::Sound deathSound; + sf::Sound walkSound; + sf::Sound changeWeaponSound; + sf::Sound slowMoSound; + sf::Sound unSlowMoSound; + sf::Sound fullHealthSound; + sf::Sound fullAbilitySound; + + + Point4D oldVelocity; + + std::string _name = "im"; + + std::vector> _weapons; + uint8_t _selectedWeapon = 0; + + std::function damagePlayerCallBack; + std::function addTraceCallBack; + std::function takeBonusCallBack; + + +public: + Player() { + loadObj("../obj/cube.obj", "", Point4D{0.5, 1.9, 0.5}); + setAcceleration(Point4D{0, -g, 0}); + setCollision(true); + setVisible(false); + setColor({240, 168, 168}); + }; + + void update(); + + void attachCamera(std::shared_ptr& camera, std::shared_ptr screen) { + _camera = camera; + _screen = std::move(screen); + + camera->translateToPoint(position() + Point4D{0, 1.8, 0}); + this->attach(camera); + } + + void attachWorld(const std::shared_ptr& world, const Point4D& pos = Point4D{0, 30, 0}) { + _world = world; + + translate(pos); + + initWeapons(); + + changeWeaponSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/change_weapon.ogg")); + + slowMoSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/slow_mo.ogg")); + unSlowMoSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/unslow_mo.ogg")); + + fullHealthSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/fullHealth.ogg")); + fullAbilitySound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/fullAbility.ogg")); + + setCollisionCallBack([this](const std::string& objName, const std::shared_ptr& obj) {collisionWithObject(objName, obj);}); + } + + void setHealth(double h) { + _health = h; + } + void setAbility(double a) { + _ability = a; + } + + void setFullHealth() { + _health = _healthMax; + fullHealthSound.play(); + } + void setFullAbility() { + _ability = _abilityMax; + fullAbilitySound.play(); + } + + + void setDamagePlayerCallBack(std::function hit) { + damagePlayerCallBack = std::move(hit); + } + + void setAddTraceCallBack(std::function add) { + addTraceCallBack = std::move(add); + } + + void setTakeBonusCallBack(std::function take) { + takeBonusCallBack = std::move(take); + } + + [[nodiscard]] double health() const { return _health; } + [[nodiscard]] std::string name() const { return "Player_" + _name; } + + + std::shared_ptr camera() { return _camera; } + + // This is for situation when you want to store the position of the head but you dont have attached camera + void setHeadAngle(double a) { _headAngle = a; } + [[nodiscard]] double headAngle() const { return _headAngle; }; + + void rotateWeaponsRelativePoint(const Point4D& point4D, const Point4D& v, double val); + + + void drawStats(); + + void addKill() { _kills++; } + void addDeath() { _deaths++; } + + void playDeath(); + void playKill(); + + void collisionWithObject(const std::string& objName, const std::shared_ptr& obj); + + void addWeapon(const std::shared_ptr& weapon); + + void initWeapons(); +}; + + +#endif //MINECRAFT_3DZAVR_PLAYER_H diff --git a/README.md b/README.md new file mode 100755 index 0000000..f7f1815 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Shooter on self-written 3D engine + +

About:

+ +Source code of simple shooter on [3Dzavr game engine](https://github.com/vectozavr/3dzavr) + +![Project demonstration](img/gamePlay2.png) + + +

Installation (Windows):

+1) Download and install .exe file + + download game: https://drive.google.com/file/d/1_8zUCwxyC-eLbqswM7OBXoNIAdzpUrkI/view + + +2) Write ip and port of server in bin/connect.txt file. + + +3) Write port of the server in bin/server.txt file (only for computer where the server will be running). + + +3) Enjoy gaming! + +

Control:

+ +SHIFT - slow motion (this ability is not infinite: its bar is next to hp) + +E & Q or keys <- -> - change weapon + +R - recharge + +Player control is standard. + +

Research source code:

+ +1) Download and install OpenAL library for SFML sound support (in current version you can't setup this engine without OpenAL) + + openal: https://openal.org/downloads/ + + +2) Install SFML on your computer (The compiler versions have to match 100%): + + sfml: https://www.sfml-dev.org/download.php + + +2) Open CLion or Visual Studio + + CLion (recommended): https://www.jetbrains.com/clion/ + + Visual Studio: https://visualstudio.microsoft.com/ru/ + + +3) Clone this repository + + rep url: https://github.com/vectozavr/shooter + + +4) Built project + +For any issues, please, create new issue in this repository. + +Demos: + +Online: +![Project demonstration](img/gamePlay4.png) + +GamePlay: +![Project demonstration](img/gamePlay3.png) +![Project demonstration](img/gamePlay5.png) +![Project demonstration](img/gamePlay6.png) +![Project demonstration](img/gamePlay7.png) diff --git a/Rifle.cpp b/Rifle.cpp new file mode 100755 index 0000000..79d30ed --- /dev/null +++ b/Rifle.cpp @@ -0,0 +1,20 @@ +// +// Created by Иван Ильин on 06.06.2021. +// + +#include +#include "Rifle.h" + +Rifle::Rifle(int ammo, const std::string &weaponName) : Weapon(weaponName, "../obj/rifle.obj", "../obj/rifle_mat.txt", Point4D{3, 3, 3}, Point4D{-1.2, 1, 0.3}, Point4D{0, M_PI, 0}) { + fireSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/shotgun.ogg")); + reloadSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/reload_ak47.ogg")); + + _initialPack = 5; + _clipCapacity = 1; // how much ammo can be stored in one clip + _stockAmmo = ammo - _clipCapacity; // how much ammo do you have in stock + _clipAmmo = _clipCapacity; // how much ammo do you have in current clip + _reloadTime = 1; + _fireDelay = 1; // time delay between fires + _damage = 30000; + _spreading = 0.5; +} diff --git a/Rifle.h b/Rifle.h new file mode 100755 index 0000000..eb1e269 --- /dev/null +++ b/Rifle.h @@ -0,0 +1,16 @@ +// +// Created by Иван Ильин on 06.06.2021. +// + +#ifndef SHOOTER_RIFLE_H +#define SHOOTER_RIFLE_H + +#include "Weapon.h" + +class Rifle : public Weapon { +public: + explicit Rifle(int ammo = 5, const std::string& weaponName = "rifle"); +}; + + +#endif //SHOOTER_3DZAVR_RIFLE_H diff --git a/Server.cpp b/Server.cpp new file mode 100755 index 0000000..6df97ea --- /dev/null +++ b/Server.cpp @@ -0,0 +1,166 @@ +// +// Created by Иван Ильин on 25.05.2021. +// + +#include "Server.h" +#include "utils/Log.h" + +void Server::broadcast() { + sf::Packet updatePacket; + updatePacket << MsgType::Update; + + for (auto& player : _players) + { + //player.second->setHealth(player.second->health() + (Time::time() - _lastBroadcast)/100); + updatePacket << player.first << player.second->position().x() << player.second->position().y() << player.second->position().z() << player.second->health() << player.second->angle().y() << player.second->headAngle(); + } + + for (auto& player : _players) + { + _socket.send(updatePacket, player.first); + } +} + + +void Server::processConnect(sf::Uint16 targetId) { + sf::Packet sendPacket1, sendPacket2; + sf::Packet extraPacket; + + // players init + extraPacket << MsgType::NewClient << targetId; + sendPacket1 << MsgType::Init << targetId; + _players.insert({ targetId, std::make_shared() }); + for (const auto& player : _players) + { + sendPacket1 << player.first << player.second->position().x() << player.second->position().y() << player.second->position().z() << player.second->health(); + if (player.first != targetId) + _socket.sendRely(extraPacket, player.first); + } + _socket.sendRely(sendPacket1, targetId); + + + // bonuses init + sendPacket2 << MsgType::InitBonuses; + for(auto& bonus : _bonuses) { + if(bonus.second.onTheMap) + sendPacket2 << bonus.first << bonus.second.position.x() << bonus.second.position.y() << bonus.second.position.z(); + } + _socket.sendRely(sendPacket2, targetId); + +} + +void Server::processClientUpdate(sf::Uint16 senderId, sf::Packet& packet) { + double buf[5]; + + packet >> buf[0] >> buf[1] >> buf[2] >> buf[3] >> buf[4]; + _players.at(senderId)->translateToPoint(Point4D{ buf[0], buf[1], buf[2] }); + _players.at(senderId)->rotateToAngle(Point4D{0, buf[3], 0}); + _players.at(senderId)->setHeadAngle(buf[4]); +} + +void Server::processDisconnect(sf::Uint16 senderId) { + sf::Packet sendPacket; + + sendPacket << MsgType::Disconnect << senderId; + _players.erase(senderId); + for (const auto& player : _players) + _socket.sendRely(sendPacket, player.first); +} + + +void Server::processCustomPacket(MsgType type, sf::Packet& packet, sf::Uint16 senderId) { + sf::Packet sendPacket; + int buff[3]; + double dbuff[10]; + sf::Uint16 targetId; + double damage; + std::string tmp; + double newHealth; + + switch (type) { + case MsgType::Damage: + packet >> targetId >> damage; + newHealth = _players[targetId]->health() - damage; + if(newHealth > 0) { + _players[targetId]->setHealth(newHealth); + } + else { + _players[targetId]->setFullHealth(); + _players[targetId]->setFullAbility(); + _players[targetId]->addDeath(); + _players[senderId]->addKill(); + + sendPacket << MsgType::Kill << targetId << senderId; + for (auto& player : _players) + _socket.send(sendPacket, player.first); + } + break; + case MsgType::FireTrace: + packet >> dbuff[0] >> dbuff[1] >> dbuff[2] >> dbuff[3] >> dbuff[4] >> dbuff[5]; + sendPacket << MsgType::FireTrace << dbuff[0] << dbuff[1] << dbuff[2] << dbuff[3] << dbuff[4] << dbuff[5]; + for (auto& player : _players) { + if(player.first != senderId) + _socket.send(sendPacket, player.first); + } + + break; + case MsgType::RemoveBonus: + packet >> tmp; + + if(tmp.find("Bonus_hill") != std::string::npos) { + _players[senderId]->setFullHealth(); + } + if(tmp.find("Bonus_ability") != std::string::npos) { + _players[senderId]->setFullAbility(); + } + + _bonuses[tmp].onTheMap = false; + _bonuses[tmp].lastTake = Time::time(); + sendPacket << MsgType::RemoveBonus << tmp; + for (auto& player : _players) { + if(player.first != senderId) + _socket.send(sendPacket, player.first); + } + break; + } +} + +void Server::processStop() { + for (auto it = _players.begin(); it != _players.end();) + _players.erase(it++); +} + +void Server::generateBonuses() { + _bonuses.insert({"Bonus_gun_1", {Point4D(-10, -2, -15), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_gun_2", {Point4D(10, -2, 15), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_shotgun_1", {Point4D(-10, 13, -24), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_shotgun_2", {Point4D(10, 13, 24), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_ak47_1", {Point4D(-25, 30, 50), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_ak47_2", {Point4D(25, 30, -50), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_gold_ak47_1", {Point4D(-35, 80, 25), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_gold_ak47_2", {Point4D(35, 80, -25), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_rifle_1", {Point4D(40, -2, 45), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_rifle_2", {Point4D(-40, -2, -45), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_hill_1", {Point4D(-40, -2, 45), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_hill_2", {Point4D(40, -2, -45), -2*_bonusRechargeTime, true}}); + + _bonuses.insert({"Bonus_ability_1", {Point4D(25, 18, -33), -2*_bonusRechargeTime, true}}); + _bonuses.insert({"Bonus_ability_2", {Point4D(-25, 18, 33), -2*_bonusRechargeTime, true}}); +} + +void Server::updateInfo() { + for(auto& bonus : _bonuses) { + if(!bonus.second.onTheMap && abs(Time::time() - bonus.second.lastTake) > _bonusRechargeTime) { + sf::Packet sendPacket; + sendPacket << MsgType::AddBonus << bonus.first << bonus.second.position.x() << bonus.second.position.y() << bonus.second.position.z(); + for (const auto& player : _players) + _socket.sendRely(sendPacket, player.first); + bonus.second.onTheMap = true; + } + } +} diff --git a/Server.h b/Server.h new file mode 100755 index 0000000..05f9d36 --- /dev/null +++ b/Server.h @@ -0,0 +1,43 @@ +// +// Created by Иван Ильин on 25.05.2021. +// + +#ifndef SHOOTER_SERVER_H +#define SHOOTER_SERVER_H + +#include "network/ServerUDP.h" +#include "Player.h" +#include "Bonus.h" + +struct BonusInfo { + Point4D position; + double lastTake; + bool onTheMap; +}; + +class Server : public ServerUDP { +private: + std::map> _players{}; + std::map _bonuses{}; + double _bonusRechargeTime = 60; + +public: + Server() = default; + + void broadcast() override; + + void processConnect(sf::Uint16 senderId) override; + void processClientUpdate(sf::Uint16 senderId, sf::Packet& packet) override; + void processDisconnect(sf::Uint16 senderId) override; + + void processCustomPacket(MsgType type, sf::Packet& packet, sf::Uint16 senderId) override; + + void processStop() override; + + void generateBonuses(); + + void updateInfo() override; +}; + + +#endif //MINECRAFT_3DZAVR_SERVER_H diff --git a/Shotgun.cpp b/Shotgun.cpp new file mode 100755 index 0000000..bcf4eca --- /dev/null +++ b/Shotgun.cpp @@ -0,0 +1,56 @@ +// +// Created by Иван Ильин on 02.06.2021. +// + +#include +#include "Shotgun.h" + +using namespace std; + +Shotgun::Shotgun(int ammo, const std::string& weaponName) : Weapon(weaponName, "../obj/shotgun.obj", "../obj/shotgun_mat.txt", Point4D{3, 3, 3}, Point4D{-0.95, 1.3, -0.6}, Point4D{0, M_PI, 0}) { + fireSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/shotgun.ogg")); + reloadSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/reload_shotgun.ogg")); + + //reloadSound.setVolume(30); + _initialPack = 15; + _clipCapacity = 1; // how much ammo can be stored in one clipx + _stockAmmo = ammo - _clipCapacity; // how much ammo do you have in stock + _clipAmmo = _clipCapacity; // how much ammo do you have in current clip + _reloadTime = 1; + _fireDelay = 1; // time delay between fires + _damage = 400; + _spreading = 5; +} + +std::map +Shotgun::processFire(const std::shared_ptr &world, const std::shared_ptr &camera) { + std::map damagedPlayers; + + for(int i = 0; i < 15; i++) { + //generate random vector + Point4D randV(10*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX), 10*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX), 10*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX)); + + // damage player + auto rayCast = world->rayCast(camera->position(), camera->position() + camera->lookAt() * 1000 + randV); + if (rayCast.second.find("Player") != std::string::npos) { + damagedPlayers[rayCast.second] += _damage / (1.0 + (camera->position() - rayCast.first).abs()); + } + + // add trace line + Point4D to = rayCast.first.w() == -1 ? camera->position() + camera->lookAt() * 1000 + randV: rayCast.first; + string traceName = _name + "_trace_nr_" + std::to_string(fireTraces++); + Point4D from = _objects[_name + "_" + to_string(9)]->position() + + _objects[_name + "_" + to_string(9)]->triangles()[0][0]; + world->addMesh(make_shared(Mesh::LineTo(from, to, 0.05)), traceName); + (*world)[traceName]->setCollider(false); + + // remove trace line after some time + (*world)[traceName]->a_color("color_trace", {255, 255, 255, 0}, 1, Animation::None, Animation::linear); + (*world)["map_0"]->a_function(traceName + "delete", [world, traceName]() { deleteTrace(world, traceName); }, + 1, 2); + addTraceCallBack(from, to); + } + + + return damagedPlayers; +} diff --git a/Shotgun.h b/Shotgun.h new file mode 100755 index 0000000..bef1fa8 --- /dev/null +++ b/Shotgun.h @@ -0,0 +1,17 @@ +// +// Created by Иван Ильин on 02.06.2021. +// + +#ifndef SHOOTER_SHOTGUN_H +#define SHOOTER_SHOTGUN_H + +#include "Weapon.h" + +class Shotgun : public Weapon { +public: + explicit Shotgun(int ammo = 15, const std::string& weaponName = "shotgun"); + std::map processFire(const std::shared_ptr& world, const std::shared_ptr& camera) override; +}; + + +#endif //SHOOTER_3DZAVR_SHOTGUN_H diff --git a/Weapon.cpp b/Weapon.cpp new file mode 100755 index 0000000..2565da5 --- /dev/null +++ b/Weapon.cpp @@ -0,0 +1,127 @@ +// +// Created by Иван Ильин on 01.06.2021. +// + +#include +#include "Weapon.h" +#include "ResourceManager.h" +#include "utils/Log.h" + +using namespace std; + +Weapon::Weapon(const std::string& weaponName, const std::string& objFileName, const std::string& matFileName, const Point4D& scale, const Point4D& translate, const Point4D& rotate) { + _name = weaponName; + auto objs = Mesh::LoadObjects(objFileName, matFileName, scale); + for(int i = 0; i < objs.size(); i++) { + string meshName = _name + "_" + to_string(i); + objs[i]->setCollider(false); + + //transforms + objs[i]->rotate(rotate); + objs[i]->translate(translate); + + _objects.insert({meshName, objs[i]}); + } + noAmmoSound.setBuffer(*ResourceManager::loadSoundBuffer("../sound/weapons/no_ammo.ogg")); +} + +std::map Weapon::fire(const std::shared_ptr& world, const std::shared_ptr& camera) { + if(_clipAmmo == 0) { + reload(); + if(_clipAmmo == 0) + noAmmoSound.play(); + } + + if(_clipAmmo <= 0 || abs(Time::time() - _lastFireTime) < _fireDelay || abs(Time::time() - _lastReloadTime) < _reloadTime) + return std::map(); + + _lastFireTime = Time::time(); + _clipAmmo--; + + fireSound.play(); + Log::log("Weapon::fire (" + std::to_string(_stockAmmo) + " : " + std::to_string(_clipAmmo) + ")"); + + return processFire(world, camera); +} + +void Weapon::reload() { + if (_stockAmmo == 0 || abs(Time::time() - _lastReloadTime) < _reloadTime) + return; + if(_clipCapacity - _clipAmmo <= _stockAmmo) { + _stockAmmo -= _clipCapacity - _clipAmmo; + _clipAmmo = _clipCapacity; + } else { + _clipAmmo += _stockAmmo; + _stockAmmo = 0; + } + + reloadSound.play(); + Log::log("Weapon::reload (" + std::to_string(_stockAmmo) + " : " + std::to_string(_clipAmmo) + ")"); + _lastReloadTime = Time::time(); +} + +void Weapon::addToWorld(const shared_ptr &world) { + for(auto& obj : _objects) { + world->addMesh(obj.second, obj.first); + } +} + +void Weapon::removeFromWorld(const shared_ptr &world) { + for(auto& obj : _objects) { + world->removeMeshInstantly(obj.first); + } +} + + +void Weapon::attachToPlayer(Mesh &player) { + for(auto& obj : _objects) { + player.attach(obj.second); + } +} + +void Weapon::rotate(const Point4D& point4D, double val) { + for(auto& mesh : _objects) + mesh.second->rotate(point4D, val); +} + +void Weapon::translate(const Point4D &point4D) { + for(auto& mesh : _objects) + mesh.second->translate(point4D); +} + +void Weapon::deleteTrace(const shared_ptr &world, const std::string& traceName) { + world->removeMesh(traceName); +} + +void Weapon::rotateRelativePoint(const Point4D &point4D, const Point4D &v, double val) { + for(auto& mesh : _objects) + mesh.second->rotateRelativePoint(point4D, v, val); +} + +std::map Weapon::processFire(const shared_ptr &world, const shared_ptr &camera) { + std::map damagedPlayers; + + //generate random vector + Point4D randV(10.0*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX), 10.0*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX), 10.0*_spreading*(1.0 - 2.0*(double)rand()/RAND_MAX)); + + // damage player + auto rayCast = world->rayCast(camera->position(), camera->position() + camera->lookAt() * 1000 + randV); + if(rayCast.second.find("Player") != std::string::npos) { + damagedPlayers[rayCast.second] += _damage/(1.0 + (camera->position() - rayCast.first).abs()); + } + + // add trace line + Point4D to = rayCast.first.w() == -1 ? camera->position() + camera->lookAt() * 1000 + randV: rayCast.first; + string traceName = _name + "_trace_nr_" + std::to_string(fireTraces++); + Point4D from = _objects[_name + "_" + to_string(_objects.size()-1)]->position() + _objects[_name + "_" + to_string(_objects.size()-1)]->triangles()[0][0]; + world->addMesh(make_shared(Mesh::LineTo(from, to, 0.05)), traceName); + (*world)[traceName]->setCollider(false); + + // remove trace line after some time + (*world)[traceName]->a_color("color_trace", {255, 255, 255, 0}, 1, Animation::None, Animation::linear); + (*world)["Player_im"]->a_function(traceName + "delete", [world, traceName](){deleteTrace(world, traceName); }, 1, 2); + + addTraceCallBack(from, to); + + return damagedPlayers; +} diff --git a/Weapon.h b/Weapon.h new file mode 100755 index 0000000..ff4db0c --- /dev/null +++ b/Weapon.h @@ -0,0 +1,80 @@ +// +// Created by Иван Ильин on 01.06.2021. +// + +#ifndef SHOOTER_WEAPON_H +#define SHOOTER_WEAPON_H + + +#include +#include +#include +#include +#include "Mesh.h" +#include "utils/Time.h" + +class Weapon { +protected: + int _initialPack = 100; // how much ammo do you have when you find the weapon + + int _clipCapacity = 30; // how much ammo can be stored in one clip + int _stockAmmo = _initialPack - _clipCapacity; // how much ammo do you have in stock + int _clipAmmo = _clipCapacity; // how much ammo do you have in current clip + + double _reloadTime = 3; + double _fireDelay = 0.1; // time delay between fires + double _damage = 300; + + double _spreading = 2.0; + + [[maybe_unused]] double _firePower = M_PI/100; + + std::string _name = "Weapon_name"; + std::map> _objects; + + double _lastFireTime = -INFINITY; + double _lastReloadTime = -INFINITY; + + sf::Sound fireSound; + sf::Sound reloadSound; + sf::Sound noAmmoSound; + + static void deleteTrace(const std::shared_ptr &world, const std::string& traceName); + + int fireTraces = 0; + + std::function addTraceCallBack; + +public: + Weapon(const std::string& weaponName, const std::string& objFileName, const std::string& matFileName, const Point4D& scale, const Point4D& translate, const Point4D& rotate); + + std::map fire(const std::shared_ptr& world, const std::shared_ptr& camera); + void reload(); + + void addToWorld(const std::shared_ptr& world); + void removeFromWorld(const std::shared_ptr& world); + + void attachToPlayer(Mesh& player); + + [[nodiscard]] std::pair balance() const{ return std::make_pair(_clipAmmo, _stockAmmo); } + + void rotateRelativePoint(const Point4D& point4D, const Point4D& v, double val); + + void rotate(const Point4D& point4D, double val); + void translate(const Point4D& point4D); + + void setAddTraceCallBack(std::function add) { + addTraceCallBack = std::move(add); + } + + [[nodiscard]] std::string name() const { return _name; } + + void addAmmo(int ammoAdd) { _stockAmmo += ammoAdd; } + + virtual std::map processFire(const std::shared_ptr& world, const std::shared_ptr& camera); + + [[nodiscard]] int initialPack() const {return _initialPack; } +}; + + +#endif //SHOOTER_3DZAVR_WEAPON_H diff --git a/engine/Camera.cpp b/engine/Camera.cpp new file mode 100755 index 0000000..61c14cc --- /dev/null +++ b/engine/Camera.cpp @@ -0,0 +1,256 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#include "Camera.h" +#include "utils/Log.h" + +std::vector &Camera::project(Mesh& mesh, Screen::ViewMode mode) { + + if(!ready) { + Log::log("Camera::project(): cannot project tris without camera initialization ( Camera::init() ) "); + return this->triangles; + } + + if(!mesh.isVisible()) + return this->triangles; + + // Model transform matrix: translate tris in the origin of mesh. + Matrix4x4 M = Matrix4x4::Translation(mesh.position()); + VM = V * M; + + // We don't want to waste time re-allocating memory every time + std::vector clippedTriangles, tempBuffer; + for(auto& t : mesh.triangles()) { + + double dot = t.norm().dot((mesh.position() + t[0] - p_position).normalize()); + + if(dot > 0) + continue; + + Triangle clipped[2]; + // It needs to be cleared because it's reused through iterations. Usually it doesn't free memory. + clippedTriangles.clear(); + + // In the beginning we need to to translate triangle from world coordinate to our camera system: + // After that we apply clipping for all planes from clipPlanes + clippedTriangles.emplace_back(t * VM); + for(auto& plane : clipPlanes) + { + while(!clippedTriangles.empty()) + { + clipped[0] = clippedTriangles.back(); + clipped[1] = clipped[0]; + clippedTriangles.pop_back(); + int additional = plane.clip(clipped[0], clipped[1]); + + for(int i = 0; i < additional; i++) + tempBuffer.emplace_back(clipped[i]); + } + clippedTriangles.swap(tempBuffer); + } + + for(auto& clippedTriangle : clippedTriangles) { + if(mode != Screen::ViewMode::Clipped) { + clippedTriangle.color = sf::Color(clippedTriangle.color.r * (0.3 * std::abs(dot) + 0.7), + clippedTriangle.color.g * (0.3 * std::abs(dot) + 0.7), + clippedTriangle.color.b * (0.3 * std::abs(dot) + 0.7), + (mode == Screen::ViewMode::Transparency || + mode == Screen::ViewMode::Normals) ? 100 : clippedTriangle.color.a); + } + + // Finally its time to project our clipped colored triangle from 3D -> 2D + // and transform it's coordinate to screen space (in pixels): + clippedTriangle *= SP; + + clippedTriangle[0] /= clippedTriangle[0].w(); + clippedTriangle[1] /= clippedTriangle[1].w(); + clippedTriangle[2] /= clippedTriangle[2].w(); + + triangles.emplace_back(clippedTriangle); + } + } + + return this->triangles; +} + +void Camera::init(int width, int height, double fov, double ZNear, double ZFar) { + // We need to init camera only after creation or changing width, height, fov, ZNear or ZFar. + // Because here we calculate matrix that does not change during the motion of _objects or camera + w = width; h = height; + aspect = (double)width / (double)height; + P = Matrix4x4::Projection(fov, aspect, ZNear, ZFar); + S = Matrix4x4::ScreenSpace(width, height); + + SP = S * P; // screen-space-projections matrix + + // This is planes for clipping tris. + // Motivation: we are not interest in tris that we cannot see. + clipPlanes.emplace_back(Plane(Point4D{0, 0, 1}, Point4D{0, 0, ZNear})); // near plane + clipPlanes.emplace_back(Plane(Point4D{0, 0, -1}, Point4D{0, 0, ZFar})); // far plane + + double thetta1 = M_PI*fov*0.5/180.0; + double thetta2 = atan(aspect*tan(thetta1)); + clipPlanes.emplace_back(Plane(Point4D{-cos(thetta2), 0, sin(thetta2)}, Point4D{0, 0, 0})); // left plane + clipPlanes.emplace_back(Plane(Point4D{cos(thetta2), 0, sin(thetta2)}, Point4D{0, 0, 0})); // right plane + clipPlanes.emplace_back(Plane(Point4D{0, cos(thetta1), sin(thetta1)}, Point4D{0, 0, 0})); // down plane + clipPlanes.emplace_back(Plane(Point4D{0, -cos(thetta1), sin(thetta1)},Point4D{0, 0, 0})); // up plane + + ready = true; + Log::log("Camera::init(): camera successfully initialized."); +} + +std::vector &Camera::sorted() { + + // Sort tris from back to front + // This is some replacement for Z-buffer + std::sort(triangles.begin(), triangles.end(), [](Triangle &t1, Triangle &t2) + { + std::vector v_z1({t1[0].z(), t1[1].z(), t1[2].z()}); + std::vector v_z2({t2[0].z(), t2[1].z(), t2[2].z()}); + + std::sort(v_z1.begin(), v_z1.end()); + std::sort(v_z2.begin(), v_z2.end()); + + double a = 1; + double b = 1; + double c = 1; + + double z1 = (a*v_z1[0] + b*v_z1[1] + c*v_z1[2]); + double z2 = (a*v_z2[0] + b*v_z2[1] + c*v_z2[2]); + + return z1 > z2; + }); + + return triangles; +} + +void Camera::record() { + // Cleaning all tris and recalculation of View matrix + // That is like preparation for new camera shot: we need to set + // the position of camera and insert new cartridge for photo. + triangles.clear(); + V = Matrix4x4::View(p_left, p_up, p_lookAt, p_position); +} + +void Camera::rotateX(double rx) { + p_angle = Point4D{p_angle.x() + rx, p_angle.y(), p_angle.z()}; + p_left = Matrix4x4::RotationX(rx) * p_left; + p_up = Matrix4x4::RotationX(rx) * p_up; + p_lookAt = Matrix4x4::RotationX(rx) * p_lookAt; +} + +void Camera::rotateY(double ry) { + p_angle = Point4D{p_angle.x(), p_angle.y() + ry, p_angle.z()}; + p_left = Matrix4x4::RotationY(ry) * p_left; + p_up = Matrix4x4::RotationY(ry) * p_up; + p_lookAt = Matrix4x4::RotationY(ry) * p_lookAt; +} + +void Camera::rotateZ(double rz) { + p_angle = Point4D{p_angle.x(), p_angle.y(), p_angle.z() + rz}; + p_left = Matrix4x4::RotationZ(rz) * p_left; + p_up = Matrix4x4::RotationZ(rz) * p_up; + p_lookAt = Matrix4x4::RotationZ(rz) * p_lookAt; +} + +void Camera::rotate(double rx, double ry, double rz) { + rotateX(rx); + rotateY(ry); + rotateZ(rz); + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(position(), Point4D{rx, ry, rz}); + +} + +void Camera::rotate(const Point4D& r) { + rotate(r.x(), r.y(), r.z()); +} + + +void Camera::rotate(const Point4D& v, double rv) { + p_left = Matrix4x4::Rotation(v, rv) * p_left; + p_up = Matrix4x4::Rotation(v, rv) * p_up; + p_lookAt = Matrix4x4::Rotation(v, rv) * p_lookAt; + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(position(), v, rv); +} + +void Camera::rotateLeft(double rl) { + p_angleLeftUpLookAt = Point4D{p_angleLeftUpLookAt.x() + rl, p_angleLeftUpLookAt.y(), p_angleLeftUpLookAt.z()}; + + rotate(p_left, rl); + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(position(), p_left, rl); +} + +void Camera::rotateUp(double ru) { + p_angleLeftUpLookAt = Point4D{p_angleLeftUpLookAt.x(), p_angleLeftUpLookAt.y() + ru, p_angleLeftUpLookAt.z()}; + rotate(p_up, ru); + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(position(), p_up, ru); +} + +void Camera::rotateLookAt(double rlAt) { + p_angleLeftUpLookAt = Point4D{p_angleLeftUpLookAt.x(), p_angleLeftUpLookAt.y(), p_angleLeftUpLookAt.z() + rlAt}; + rotate(p_lookAt, rlAt); + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(position(), p_lookAt, rlAt); +} + +void Camera::rotateRelativePoint(const Point4D &s, double rx, double ry, double rz) { + p_angle += Point4D{rx, ry, rz}; + + // Translate XYZ by vector r1 + Point4D r1 = p_position - s; + // In translated coordinate system we rotate camera and position + Point4D r2 = Matrix4x4::Rotation(rx, ry, rz)*r1; + rotate(rx, ry, rz); + + // After rotation we translate XYZ by vector -r2 and recalculate position + p_position = s + r2; + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(s, Point4D{rx, ry, rz}); +} + +void Camera::rotateRelativePoint(const Point4D &s, const Point4D &r) { + rotateRelativePoint(s, r.x(), r.y(), r.z()); +} + +void Camera::rotateRelativePoint(const Point4D &s, const Point4D &v, double r) { + // Translate XYZ by vector r1 + Point4D r1 = p_position - s; + // In translated coordinate system we rotate camera and position + Point4D r2 = Matrix4x4::Rotation(v, r)*r1; + rotate(v, r); + + // After rotation we translate XYZ by vector -r2 and recalculate position + p_position = s + r2; + + if(v_attached.empty()) + return; + for(auto& attached : v_attached) + attached->rotateRelativePoint(s, v, r); +} + +void Camera::translateToPoint(const Point4D &point) { + translate(point - p_position); +} diff --git a/engine/Camera.h b/engine/Camera.h new file mode 100755 index 0000000..9894829 --- /dev/null +++ b/engine/Camera.h @@ -0,0 +1,98 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#ifndef ENGINE_CAMERA_H +#define ENGINE_CAMERA_H + +#include +#include "Screen.h" +#include "Plane.h" +#include "Mesh.h" + +class Camera : public Object, public Animatable{ +private: + Point4D p_angleLeftUpLookAt; + + Point4D p_left = Point4D{1, 0, 0, 0}; // internal X + Point4D p_up = Point4D{0, 1, 0, 0}; // internal Y + Point4D p_lookAt = Point4D{0, 0, 1, 0}; // internal Z + + Matrix4x4 S; // screen space matrix + Matrix4x4 P; // projections matrix + Matrix4x4 V; // camera matrix + + double aspect = 0; + + // To accelerate calculations we can use precalculated matrix that does not chance + Matrix4x4 SP; // screen-space-projections matrix + Matrix4x4 VM; // camera-model-animation matrix + + std::vector triangles; + std::vector clipPlanes; + + bool ready = false; + + double w = 0; + double h = 0; +public: + Camera() = default; + Camera(const Camera& camera) = delete; + + + void init(int width, int height, double fov = 110.0, double ZNear = 0.1, double ZFar = 5000.0); + + std::vector& project(Mesh& mesh, Screen::ViewMode mode); + + void record(); + + [[nodiscard]] int buffSize() const { return triangles.size(); } + std::vector& sorted(); + + [[nodiscard]] Point4D position() const override { return p_position; } + [[nodiscard]] Point4D angle() const override { return p_angle; } + [[nodiscard]] Point4D angleLeftUpLookAt() const { return p_angleLeftUpLookAt; } + + [[nodiscard]] Point4D eye() const { return p_position; } + [[nodiscard]] Point4D left() const { return p_left; } + [[nodiscard]] Point4D right() const { return -p_left; } + [[nodiscard]] Point4D up() const { return p_up; } + [[nodiscard]] Point4D down() const { return -p_up; } + [[nodiscard]] Point4D lookAt() const { return p_lookAt; } + + void translate(const Point4D& dv) override { + p_position += dv; + + if(v_attached.empty()) + return; + for(const auto& attached : v_attached) + attached->translate(dv); + } + void translate(double dx, double dy, double dz) { + translate(Point4D{dx, dy, dz}); + } + + void translateToPoint(const Point4D& point); + + void rotateX(double rx); + void rotateY(double ry); + void rotateZ(double rz); + void rotate(double rx, double ry, double rz); + void rotate(const Point4D& r) override; + + void rotate(const Point4D& v, double rv) override; + + void rotateLeft(double rl); + void rotateUp(double ru); + void rotateLookAt(double rlAt); + + // Rotate mesh around XYZ by (rx, ry, rz) radians relative val 'point4D' + void rotateRelativePoint(const Point4D& s, double rl, double ru, double rlAt); + // Rotate mesh around XYZ by (r.x, r.y, r.z) radians relative val 'point4D' + void rotateRelativePoint(const Point4D& s, const Point4D& r) override; + // Rotate mesh around normalised vector 'v' by 'r' radians relative val 'point4D' + void rotateRelativePoint(const Point4D& s, const Point4D& v, double r) override; +}; + + +#endif //INC_3DZAVR_CAMERA_H diff --git a/engine/CameraController.cpp b/engine/CameraController.cpp new file mode 100755 index 0000000..f1a87ec --- /dev/null +++ b/engine/CameraController.cpp @@ -0,0 +1,38 @@ +// +// Created by Иван Ильин on 23.01.2021. +// + +#include "CameraController.h" + +#include + +CameraController::CameraController(std::shared_ptr camera, std::shared_ptr screen) : camera(std::move(camera)), screen(std::move(screen)) { +} + +void CameraController::update() { + // Left and right + if (Screen::isKeyPressed(sf::Keyboard::A)) + camera->translate(camera->left()*Time::deltaTime()*5.0); + + if (Screen::isKeyPressed(sf::Keyboard::D)) + camera->translate(-camera->left()*Time::deltaTime()*5.0); + + // Forward and backward + if (Screen::isKeyPressed(sf::Keyboard::W)) + camera->translate(camera->lookAt()*Time::deltaTime()*5.0); + + if (Screen::isKeyPressed(sf::Keyboard::S)) + camera->translate(-camera->lookAt()*Time::deltaTime()*5.0); + + if (Screen::isKeyPressed(sf::Keyboard::LShift)) + camera->translate(0.0, -Time::deltaTime()*5.0, 0); + + if (Screen::isKeyPressed(sf::Keyboard::Space)) + camera->translate(0.0, Time::deltaTime()*5.0, 0); + + // Mouse movement + Point4D disp = screen->getMouseDisplacement(); + + camera->rotateY(-disp.x()/1000.0); + camera->rotateLeft(disp.y()/1000.0); +} diff --git a/engine/CameraController.h b/engine/CameraController.h new file mode 100755 index 0000000..9fa9ce2 --- /dev/null +++ b/engine/CameraController.h @@ -0,0 +1,21 @@ +// +// Created by Иван Ильин on 23.01.2021. +// + +#ifndef ENGINE_CAMERACONTROLLER_H +#define ENGINE_CAMERACONTROLLER_H + +#include "Camera.h" +#include "Screen.h" + +class CameraController { +private: + std::shared_ptr camera; + std::shared_ptr screen; +public: + CameraController(std::shared_ptr camera, std::shared_ptr screen); + void update(); +}; + + +#endif //INC_3DZAVR_CAMERACONTROLLER_H diff --git a/engine/Engine.cpp b/engine/Engine.cpp new file mode 100755 index 0000000..3c30a3a --- /dev/null +++ b/engine/Engine.cpp @@ -0,0 +1,105 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#include "Engine.h" +#include "utils/Time.h" +#include +#include "ResourceManager.h" +#include "physics/Solver.h" + +Engine::Engine() { + screen = std::make_shared(); + world = std::make_shared(); + camera = std::make_shared(); +} + +void Engine::create(int screenWidth, int screenHeight, const std::string &name, bool verticalSync, sf::Color background, sf::Uint32 style) { + screen->open(screenWidth, screenHeight, name, verticalSync, background, style); + + Log::log("Engine::create(): started engine (" + std::to_string(screenWidth) + " x " + std::to_string(screenHeight) + ") with name '" + name + "'."); + Time::update(); + + start(); + camera->init(screenWidth, screenHeight); + screen->getMouseDisplacement(); // We do it to set mouse position in the center (see how getMouseDisplacement() works) + + while (screen->isOpen()) { + screen->clear(); + + Time::update(); + screen->keyboardControl(); + update(Time::deltaTime()); + + world->garbageCollector(); + /* Project all mesh + * Here we project all tris for each mesh from world._objects. + * When we call camera.project(m.second), + */ + + // sometimes we dont need to update physics world + // (for example in menu or while pause) + // hence we can set 'b_updateWorld' equal to false in setUpdateWorld(bool): + if(b_updateWorld) { + camera->record(); + for (auto &m : world->objects()) { + m.second->a_update(); + camera->project(*m.second, screen->mode()); + + m.second->updatePhysicsState(); + // isCollision detection: + if (m.second->isCollision()) { + m.second->setInCollision(false); + m.second->setCollisionNormal(Point4D{0, 0, 0}); + for (auto &obj : world->objects()) { + if(obj.first != m.first) { + std::pair gjk = m.second->checkGJKCollision(obj.second); + if (gjk.first) { + if (obj.second->isCollider()) { + CollisionPoint epa = m.second->EPA(gjk.second, obj.second); + Solver::solveCollision(m.second, obj.second, epa); + } + if (m.second->collisionCallBack() != nullptr) + m.second->collisionCallBack()(obj.first, obj.second); + } + } + } + } + + } + + // draw projected mesh + for (auto &t : camera->sorted()) + screen->triangle(t); + + camera->a_update(); + + triPerSec = camera->buffSize() * Time::fps(); + + if (b_debugText) { + screen->debugText(name + "\n\n X: " + + std::to_string((camera->eye().x())) + "\n Y: " + + std::to_string((camera->eye().y())) + "\n Z: " + + std::to_string((camera->eye().z())) + "\n\n" + + std::to_string(screen->width()) + "x" + + std::to_string(screen->height()) + "\n" + + std::to_string(Time::fps()) + + " fps \n" + std::to_string((int) triPerSec) + " tris/s"); + } + } + + gui(); + screen->display(); + } + exit(); +} + +void Engine::exit() { + if(screen->isOpen()) { + screen->close(); + if(screen->isRender()) + screen->setRender(false); + } + ResourceManager::unloadAllResources(); + Log::log("Engine::exit(): exit engine (" + std::to_string(screen->width()) + " x " + std::to_string(screen->height()) + ") with name '" + screen->title() + "'."); +} diff --git a/engine/Engine.h b/engine/Engine.h new file mode 100755 index 0000000..f7dc2d2 --- /dev/null +++ b/engine/Engine.h @@ -0,0 +1,41 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#ifndef ENGINE_ENGINE_H +#define ENGINE_ENGINE_H + +#include "Screen.h" +#include "World.h" +#include "Camera.h" +#include "utils/Log.h" +#include "CameraController.h" + +class Engine { +protected: + std::shared_ptr screen; + std::shared_ptr world; + std::shared_ptr camera; + + double triPerSec = 0; + + bool b_debugText = true; + bool b_updateWorld = true; +public: + Engine(); + + virtual ~Engine() = default; + + void create(int screenWidth = 1920, int screenHeight = 1080, const std::string& name = "engine", bool verticalSync = true, sf::Color background = sf::Color(255, 255, 255), sf::Uint32 style = sf::Style::Default); + + virtual void start() {}; + virtual void update(double elapsedTime) {}; + void exit(); + void debugText(bool value) { b_debugText = value; } + void setUpdateWorld(bool value) { b_updateWorld = value; } + + virtual void gui(){} +}; + + +#endif //INC_3DZAVR_TDZAVR_H diff --git a/engine/Mesh.cpp b/engine/Mesh.cpp new file mode 100755 index 0000000..e5f333c --- /dev/null +++ b/engine/Mesh.cpp @@ -0,0 +1,308 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "utils/Log.h" + +using namespace std; + +Mesh Mesh::operator*(const Matrix4x4 &matrix4X4) const { + return Mesh(*this) *= matrix4X4; +} + +Mesh &Mesh::operator*=(const Matrix4x4 &matrix4X4) { + for (auto& t : tris) + t *= matrix4X4; + + return *this; +} + +Mesh &Mesh::loadObj(const std::string& filename, const std::string &materials, const Point4D& scale) { + + auto objects = Mesh::LoadObjects(filename, materials, scale); + for(auto& obj : objects) { + for (auto &tri : obj->triangles()) { + tris.push_back(tri); + } + } + return *this; +} + +Mesh::Mesh(const std::string& filename, const std::string &materials, const Point4D& scale){ + loadObj(filename, materials, scale); +} + +Mesh::Mesh(const vector &tries){ + tris = tries; +} + +Mesh::Mesh(const Mesh& mesh) : Animatable(mesh) { + *this = mesh; +} + +Mesh Mesh::Obj(const std::string& filename) { + return Mesh(filename); +} + +Mesh Mesh::Cube(double size) { + Mesh cube{}; + cube.tris = { + { Point4D{0.0, 0.0, 0.0, 1.0}, Point4D{0.0, 1.0, 0.0, 1.0}, Point4D{1.0, 1.0, 0.0, 1.0} }, + { Point4D{0.0, 0.0, 0.0, 1.0}, Point4D{1.0, 1.0, 0.0, 1.0}, Point4D{1.0, 0.0, 0.0, 1.0} }, + { Point4D{1.0, 0.0, 0.0, 1.0}, Point4D{1.0, 1.0, 0.0, 1.0}, Point4D{1.0, 1.0, 1.0, 1.0} }, + { Point4D{1.0, 0.0, 0.0, 1.0}, Point4D{1.0, 1.0, 1.0, 1.0}, Point4D{1.0, 0.0, 1.0, 1.0} }, + { Point4D{1.0, 0.0, 1.0, 1.0}, Point4D{1.0, 1.0, 1.0, 1.0}, Point4D{0.0, 1.0, 1.0, 1.0} }, + { Point4D{1.0, 0.0, 1.0, 1.0}, Point4D{0.0, 1.0, 1.0, 1.0}, Point4D{0.0, 0.0, 1.0, 1.0} }, + { Point4D{0.0, 0.0, 1.0, 1.0}, Point4D{0.0, 1.0, 1.0, 1.0}, Point4D{0.0, 1.0, 0.0, 1.0} }, + { Point4D{0.0, 0.0, 1.0, 1.0}, Point4D{0.0, 1.0, 0.0, 1.0}, Point4D{0.0, 0.0, 0.0, 1.0} }, + { Point4D{0.0, 1.0, 0.0, 1.0}, Point4D{0.0, 1.0, 1.0, 1.0}, Point4D{1.0, 1.0, 1.0, 1.0} }, + { Point4D{0.0, 1.0, 0.0, 1.0}, Point4D{1.0, 1.0, 1.0, 1.0}, Point4D{1.0, 1.0, 0.0, 1.0} }, + { Point4D{1.0, 0.0, 1.0, 1.0}, Point4D{0.0, 0.0, 1.0, 1.0}, Point4D{0.0, 0.0, 0.0, 1.0} }, + { Point4D{1.0, 0.0, 1.0, 1.0}, Point4D{0.0, 0.0, 0.0, 1.0}, Point4D{1.0, 0.0, 0.0, 1.0} }, + + }; + + return cube *= Matrix4x4::Scale(size, size, size); +} + +void Mesh::translate(double dx, double dy, double dz) { + p_position += Point4D(dx, dy, dz); + + if(v_attached.empty()) + return; + for(auto attached : v_attached) + attached->translate(Point4D{dx, dy, dz}); +} + +void Mesh::rotate(double rx, double ry, double rz) { + p_angle += Point4D{rx, ry, rz}; + *this *= Matrix4x4::Rotation(rx, ry, rz); +} + +void Mesh::rotate(const Point4D &r) { + p_angle += r; + *this *= Matrix4x4::Rotation(r); + + if(v_attached.empty()) + return; + for(auto attached : v_attached) + attached->rotateRelativePoint(position(), r); +} + +void Mesh::rotate(const Point4D &v, double r) { + *this *= Matrix4x4::Rotation(v, r); + + if(v_attached.empty()) + return; + for(auto attached : v_attached) + attached->rotateRelativePoint(position(), v, r); +} + +void Mesh::scale(double sx, double sy, double sz) { + *this *= Matrix4x4::Scale(sx, sy, sz); +} + +void Mesh::scale(const Point4D &s) { + *this *= Matrix4x4::Scale(s.x(), s.y(), s.z()); +} + +void Mesh::translate(const Point4D &t) { + translate(t.x(), t.y(), t.z()); +} + +Mesh &Mesh::operator=(const Mesh &mesh) { + tris = mesh.tris; + p_position = mesh.p_position; + c_color = mesh.c_color; + return *this; +} + +void Mesh::rotateRelativePoint(const Point4D &s, double rx, double ry, double rz) { + p_angle += Point4D{rx, ry, rz}; + + // Translate XYZ by vector r1 + Point4D r1 = p_position - s; + *this *= Matrix4x4::Translation(r1); + + // In translated coordinate system we rotate mesh and position + Matrix4x4 rotationMatrix = Matrix4x4::Rotation(rx, ry, rz); + Point4D r2 = rotationMatrix*r1; + *this *= rotationMatrix; + + // After rotation we translate XYZ by vector -r2 and recalculate position + *this *= Matrix4x4::Translation(-r2); + p_position = s + r2; + + if(v_attached.empty()) + return; + for(auto attached : v_attached) + attached->rotateRelativePoint(s, Point4D{rx, ry, rz}); +} + +void Mesh::rotateRelativePoint(const Point4D &s, const Point4D &r) { + p_angle += r; + rotateRelativePoint(s, r.x(), r.y(), r.z()); +} + +void Mesh::rotateRelativePoint(const Point4D &s, const Point4D &v, double r) { + // Translate XYZ by vector r1 + Point4D r1 = p_position - s; + *this *= Matrix4x4::Translation(r1); + + // In translated coordinate system we rotate mesh and position + Matrix4x4 rotationMatrix = Matrix4x4::Rotation(v, r); + Point4D r2 = rotationMatrix*r1; + *this *= rotationMatrix; + + // After rotation we translate XYZ by vector -r2 and recalculate position + *this *= Matrix4x4::Translation(-r2); + p_position = s + r2; + + if(v_attached.empty()) + return; + for(auto attached : v_attached) + attached->rotateRelativePoint(s, v, r); +} + + +void Mesh::translateToPoint(const Point4D &point) { + translate(point - p_position); +} + +void Mesh::setColor(sf::Color c) { + c_color = c; + for (auto& t : tris) + t.color = c_color; +} + +std::vector> +Mesh::LoadObjects(const string &filename, const string &materials, const Point4D &scale) { + std::vector> objects; + map maters; + + ifstream file(filename); + if (!file.is_open()) + { + Log::log("Mesh::LoadObjects(): cannot load file from " + filename); + return objects; + } + + if(!materials.empty()) { + ifstream mat(materials); + + if (!mat.is_open()) + { + Log::log("Mesh::LoadObjects(): cannot load mat from " + materials); + return objects; + } else { + while (!mat.eof()) + { + char line[128]; + mat.getline(line, 128); + + stringstream s; + s << line; + + int color[4]; + string matName; + + s >> matName >> color[0] >> color[1] >> color[2] >> color[3]; + maters.insert({matName, sf::Color(color[0],color[1],color[2], color[3])}); + } + mat.close(); + } + } + + vector verts; + std::vector tris; + sf::Color currentColor = sf::Color(255, 245, 194, 255); + + while (!file.eof()) + { + char line[128]; + file.getline(line, 128); + + stringstream s; + s << line; + + char junk; + if(line[0] == 'o') { + if(!tris.empty()) { + objects.push_back(make_shared(tris)); + objects.back()->scale(scale); + } + tris.clear(); + } + if (line[0] == 'v') + { + double x, y, z; + s >> junk >> x >> y >> z; + verts.emplace_back(x, y, z, 1); + } + if(line[0] == 'g') { + string matInfo; + s >> junk >> matInfo; + string colorName = matInfo.substr(matInfo.size()-3, 3); + currentColor = maters[matInfo.substr(matInfo.size()-3, 3)]; + } + if (line[0] == 'f') + { + int f[3]; + s >> junk >> f[0] >> f[1] >> f[2]; + tris.emplace_back(verts[f[0] - 1], verts[f[1] - 1], verts[f[2] - 1] ); + tris.back().color = currentColor; + } + } + + if(!tris.empty()) { + objects.push_back(make_shared(tris)); + objects.back()->scale(scale); + } + + file.close(); + + return objects; +} + +Mesh Mesh::LineTo(const Point4D& from, const Point4D& to, double line_width, sf::Color color) { + Mesh line; + + Point4D v1 = (to - from).normalized(); + Point4D v2 = from.cross3D(from + Point4D{1, 0, 0}).normalized(); + Point4D v3 = v1.cross3D(v2).normalized(); + + // from plane + Point4D p1 = from - v2 * line_width/2.0 - v3 * line_width/2.0; + Point4D p2 = from - v2 * line_width/2.0 + v3 * line_width/2.0; + Point4D p3 = from + v2 * line_width/2.0 + v3 * line_width/2.0; + Point4D p4 = from + v2 * line_width/2.0 - v3 * line_width/2.0; + // to plane + Point4D p5 = to - v2 * line_width/2.0 - v3 * line_width/2.0; + Point4D p6 = to - v2 * line_width/2.0 + v3 * line_width/2.0; + Point4D p7 = to + v2 * line_width/2.0 + v3 * line_width/2.0; + Point4D p8 = to + v2 * line_width/2.0 - v3 * line_width/2.0; + + line.tris = { + { p2, p4, p1 }, + { p2, p3, p4 }, + { p1, p6, p2 }, + { p1, p5, p6 }, + { p2, p6, p7 }, + { p2, p7, p3 }, + { p6, p5, p8 }, + { p6, p8, p7 }, + { p4, p3, p7 }, + { p4, p7, p8 }, + { p1, p8, p5 }, + { p1, p4, p8 } + }; + + line.setColor(color); + + return line; +} diff --git a/engine/Mesh.h b/engine/Mesh.h new file mode 100755 index 0000000..9225f32 --- /dev/null +++ b/engine/Mesh.h @@ -0,0 +1,84 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#ifndef ENGINE_MESH_H +#define ENGINE_MESH_H + +#include +#include "Triangle.h" +#include "animation/Animatable.h" +#include "physics/RigidBody.h" +#include +#include "Object.h" + +class Mesh : public Object, public Animatable, public RigidBody { +protected: + std::vector tris; + + bool _visible = true; + + sf::Color c_color = sf::Color(255, 245, 194); + + // Operations with Matrix4x4 + [[nodiscard]] Mesh operator*(const Matrix4x4& matrix4X4) const; + Mesh& operator*=(const Matrix4x4& matrix4X4); + + std::function&)> _collisionCallBack; + +public: + Mesh() = default; + Mesh(const Mesh& mesh); + + explicit Mesh(const std::vector& tries); + Mesh& operator=(const Mesh& mesh); + explicit Mesh(const std::string& filename, const std::string &materials = "", const Point4D& scale = Point4D{1, 1, 1}); + + Mesh& loadObj(const std::string& filename, const std::string &materials = "", const Point4D& scale = Point4D{1, 1, 1}); + + [[nodiscard]] std::vectorconst &triangles() const { return tris; } + [[nodiscard]] std::vector& triangles() override { return tris; } + void setTriangles(const std::vector& t) override { tris = t; } + + // Translate mesh + void translate(double dx, double dy, double dz); + void translate(const Point4D& t) override; + void translateToPoint(const Point4D& point); + // Rotate mesh around XYZ axes + void rotate(double rx, double ry, double rz); + void rotate(const Point4D& r) override; + // Rotate mesh around normalised vector 'v' by 'r' radians + void rotate(const Point4D& v, double r) override; + // Rotate mesh around XYZ by (rx, ry, rz) radians relative val 'point4D' + void rotateRelativePoint(const Point4D& point4D, double rx, double ry, double rz); + // Rotate mesh around XYZ by (r.x, r.y, r.z) radians relative val 'point4D' + void rotateRelativePoint(const Point4D& point4D, const Point4D& r) override; + // Rotate mesh around normalised vector 'v' by 'r' radians relative val 'point4D' + void rotateRelativePoint(const Point4D& point4D, const Point4D& v, double r) override; + void scale(double sx, double sy, double sz); + void scale(const Point4D& s); + + void rotateToAngle(const Point4D& v) { rotate(v - p_angle); } + + [[nodiscard]] Point4D position() const override { return p_position; } + [[nodiscard]] Point4D angle() const override { return p_angle; } + + [[nodiscard]] sf::Color color() const override { return c_color; } + void setColor(sf::Color c) override; + + Mesh static Cube(double size = 1.0); + Mesh static Obj(const std::string& filename); + Mesh static LineTo(const Point4D& from, const Point4D& to, double line_width = 0.1, sf::Color color = {150, 150, 150, 255}); + + + void setVisible(bool visibility) { _visible = visibility; } + [[nodiscard]] bool isVisible() const { return _visible; } + + std::vector> static LoadObjects(const std::string& filename, const std::string &materials = "", const Point4D& scale = Point4D{1, 1, 1}); + + [[nodiscard]] const std::function&)>& collisionCallBack() const { return _collisionCallBack; } + void setCollisionCallBack(const std::function&)>& f) { _collisionCallBack = f; } +}; + + +#endif //INC_3DZAVR_MESH_H diff --git a/engine/Object.h b/engine/Object.h new file mode 100755 index 0000000..1efbe0c --- /dev/null +++ b/engine/Object.h @@ -0,0 +1,36 @@ +// +// Created by Иван Ильин on 15.03.2021. +// + +#ifndef ENGINE_OBJECT_H +#define ENGINE_OBJECT_H + +#include +#include "utils/Point4D.h" +#include + +class Object { +protected: + std::vector> v_attached; + + Point4D p_position; + Point4D p_angle; +public: + Object() = default; + + virtual void translate(const Point4D& dv) {} + virtual void rotate(const Point4D& r) {} + virtual void rotateRelativePoint(const Point4D& point4D, const Point4D& r) {} + virtual void rotate(const Point4D& v, double rv) {} + virtual void rotateRelativePoint(const Point4D& s, const Point4D& v, double r) {} + + [[nodiscard]] Point4D position() const { return p_position; } + [[nodiscard]] Point4D angle() const { return p_angle; } + + void attach(const std::shared_ptr& object) { + v_attached.push_back(object); + } +}; + + +#endif //MINECRAFT_3DZAVR_OBJECT_H diff --git a/engine/Plane.cpp b/engine/Plane.cpp new file mode 100755 index 0000000..d262be7 --- /dev/null +++ b/engine/Plane.cpp @@ -0,0 +1,111 @@ +// +// Created by Иван Ильин on 19.01.2021. +// + +#include "Plane.h" + +Plane::Plane(const Triangle& tri) { + triangle = tri; + n = tri.norm(); + p = tri[0]; +} + +Plane::Plane(const Point4D &N, const Point4D &P) { + n = N; + p = P; +} + +Plane::Plane(const Plane &plane) { + triangle = plane.triangle; + n = plane.n; + p = plane.p; +} + +double Plane::distance(const Point4D &point4D) const { + return point4D.dot(n) - p.dot(n); +} + +std::pair Plane::intersection(const Point4D &start, const Point4D &end) { + double s_dot_n = start.dot(n); + double k = (s_dot_n - p.dot(n))/(s_dot_n - end.dot(n)); + Point4D res = start + (end - start)*k; + return std::make_pair(res, k); +} + +int Plane::clip(Triangle &tri, Triangle &additional_tri) { + n.normalize(); + + Point4D insidePoints[3]; int inside = 0; + Point4D outsidePoints[3]; int outside = 0; + + Point4D insideTextures[3]; int insideT = 0; + Point4D outsideTextures[3]; int outsideT = 0; + + double distances[3] = {distance(tri[0]), distance(tri[1]), distance(tri[2])}; + + for(int i = 0; i < 3; i++) { + if (distances[i] >= 0) { + insidePoints[inside++] = tri[i]; + insideTextures[insideT++] = tri.t[i]; + } else { + outsidePoints[outside++] = tri[i]; + outsideTextures[outsideT++] = tri.t[i]; + } + } + + if(inside == 0) { + tri.clip = Triangle::Skipped; + return 0; + } + + if(inside == 1) { + std::pair intersect1 = intersection(insidePoints[0], outsidePoints[0]); + std::pair intersect2 = intersection(insidePoints[0], outsidePoints[1]); + tri[0] = insidePoints[0]; + tri.t[0] = insideTextures[0]; + + tri[1] = intersect1.first; + tri.t[1] = insideTextures[0] + (outsideTextures[0] - insideTextures[0]) * intersect1.second; + + tri[2] = intersect2.first; + tri.t[2] = insideTextures[0] + (outsideTextures[1] - insideTextures[0]) * intersect2.second; + + tri.clip = Triangle::Cropped; + + return 1; + } + + if(inside == 2) { + std::pair intersect1 = intersection(insidePoints[0], outsidePoints[0]); + std::pair intersect2 = intersection(insidePoints[1], outsidePoints[0]); + + tri[0] = insidePoints[0]; + tri.t[0] = insideTextures[0]; + + tri[1] = intersect1.first; + tri.t[1] = insideTextures[0] + (outsideTextures[0] - insideTextures[0])*intersect1.second; + + tri[2] = insidePoints[1]; + tri.t[2] = insideTextures[1]; + + additional_tri[0] = intersect1.first; + additional_tri.t[0] = insideTextures[0] + (outsideTextures[0] - insideTextures[0])*intersect1.second; + + additional_tri[1] = intersect2.first; + additional_tri.t[1] = insideTextures[1] + (outsideTextures[0] - insideTextures[1])*intersect2.second; + + additional_tri[2] = insidePoints[1]; + additional_tri.t[2] = insideTextures[1]; + + tri.clip = Triangle::Doubled; + additional_tri.clip = Triangle::Doubled; + + return 2; + } + + if(inside == 3) { + return 1; + } + + return 0; +} diff --git a/engine/Plane.h b/engine/Plane.h new file mode 100755 index 0000000..f2fe955 --- /dev/null +++ b/engine/Plane.h @@ -0,0 +1,35 @@ +// +// Created by Иван Ильин on 19.01.2021. +// + +#ifndef ENGINE_PLANE_H +#define ENGINE_PLANE_H + +#include "utils/Point4D.h" +#include "Triangle.h" + +class Plane { +private: + // You can define plane by defining the points in 3D space + Triangle triangle; + // Or by defining normal vector and one val laying on the plane + Point4D n = Point4D{0, 0, 1, 0}; + Point4D p{}; +public: + // A plain with normal vector 'n' and val 'p' lays on the plane + Plane() = default; + Plane(const Point4D& N, const Point4D& P); + Plane(const Plane& plane); + explicit Plane(const Triangle& tri); + + [[nodiscard]] double distance(const Point4D& point4D) const; + // Point4D in space where line ('start' to 'end') intersects plain with normal vector 'n' and val 'p' lays on the plane + std::pair intersection(const Point4D& start, const Point4D& end); + int clip(Triangle& tri, Triangle& additional_tri); + + [[nodiscard]] Point4D N() const { return n; } + [[nodiscard]] Point4D P() const { return p; } +}; + + +#endif //INC_3DZAVR_PLANE_H diff --git a/engine/ResourceManager.cpp b/engine/ResourceManager.cpp new file mode 100755 index 0000000..3eb15e2 --- /dev/null +++ b/engine/ResourceManager.cpp @@ -0,0 +1,123 @@ +// +// Created by Neirokan on 09.05.2020 +// + +#include "ResourceManager.h" +#include +#include +#include +namespace ResourceManager +{ + namespace + { + std::map> _textures; + std::map> _fonts; + std::map> _soundBuffers; + std::map> _shaders; + } + + void unloadTextures() + { + for (auto & _texture : _textures) + _texture.second.reset(); + _textures.clear(); + } + + void unloadSoundBuffers() + { + for (auto & _soundBuffer : _soundBuffers) + _soundBuffer.second.reset(); + _soundBuffers.clear(); + } + + void unloadFonts() { + for (auto & _font : _fonts) + _font.second.reset(); + _fonts.clear(); + } + + void unloadShaders() { + for (auto& shader : _shaders) + shader.second.reset(); + _shaders.clear(); + } + + void unloadAllResources() + { + unloadTextures(); + unloadSoundBuffers(); + unloadFonts(); + unloadShaders(); + } + + std::shared_ptr loadTexture(const std::string& filename) + { + // If texture is already loaded - return pointer to it + auto it = _textures.find(filename); + if (it != _textures.end()) + return it->second; + + // Otherwise - try to load it. If failure - return zero + std::shared_ptr texture(new sf::Texture); + if (!texture->loadFromFile(filename)) + return nullptr; + + // If success - remember and return texture pointer + texture->setRepeated(true); + _textures.emplace(filename, texture); + + return texture; + } + + std::shared_ptr loadSoundBuffer(const std::string& filename) + { + // If sound buffer is already loaded - return pointer to it + auto it = _soundBuffers.find(filename); + if (it != _soundBuffers.end()) + return it->second; + + // Otherwise - try to load it. If failure - return zero + std::shared_ptr soundBuffer(new sf::SoundBuffer); + if (!soundBuffer->loadFromFile(filename)) + return nullptr; + + // If success - remember and return texture pointer + _soundBuffers.emplace(filename, soundBuffer); + + return soundBuffer; + } + + std::shared_ptr loadFont(const std::string& filename) { + // If font is already loaded - return pointer to it + auto it = _fonts.find(filename); + if (it != _fonts.end()) + return it->second; + + // Otherwise - try to load it. If failure - return zero + std::shared_ptr font(new sf::Font); + if (!font->loadFromFile(filename)) + return nullptr; + + // If success - remember and return texture pointer + _fonts.emplace(filename, font); + + return font; + } + + std::shared_ptr loadShader(const std::string& filename, sf::Shader::Type type) { + // If Shader is already loaded - return pointer to it + auto it = _shaders.find(filename); + if (it != _shaders.end()) + return it->second; + + // Otherwise - try to load it. If failure - return zero + std::shared_ptr shader(new sf::Shader); + if (!shader->loadFromFile(filename, type)) + return nullptr; + + // If success - remember and return texture pointer + _shaders.emplace(filename, shader); + + return shader; + } +} diff --git a/engine/ResourceManager.h b/engine/ResourceManager.h new file mode 100755 index 0000000..0d57216 --- /dev/null +++ b/engine/ResourceManager.h @@ -0,0 +1,32 @@ +// +// Created by Neirokan on 09.05.2020 +// + +#ifndef ENGINE_RESOURCEMANAGER_H +#define ENGINE_RESOURCEMANAGER_H + +#include +#include +#include + +namespace ResourceManager +{ + // Unloads all currently loaded textures. + void unloadTextures(); + void unloadSoundBuffers(); + void unloadFonts(); + void unloadShaders(); + + void unloadAllResources(); + + // Try to load texture from file. + // If success returns pointer to texture. + // Otherwise returns nullptr. + std::shared_ptr loadTexture(const std::string& filename); + std::shared_ptr loadFont(const std::string& filename); + std::shared_ptr loadSoundBuffer(const std::string& filename); + std::shared_ptr loadShader(const std::string& filename, sf::Shader::Type type); +}; + + +#endif //PSEUDO3DENGINE_RESOURCEMANAGER_H diff --git a/engine/Screen.cpp b/engine/Screen.cpp new file mode 100755 index 0000000..b8716af --- /dev/null +++ b/engine/Screen.cpp @@ -0,0 +1,205 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#include "Screen.h" +#include "utils/Time.h" +#include +#include "utils/Log.h" +#include "ResourceManager.h" +#include + + +void Screen::open(int screenWidth, int screenHeight, const std::string &name, bool verticalSync, sf::Color background, sf::Uint32 style) { + this->name = name; + w = screenWidth; + h = screenHeight; + this->background = background; + + sf::ContextSettings settings; + settings.antialiasingLevel = 8; + + window.create(sf::VideoMode(w, h), name, style, settings); + window.setVerticalSyncEnabled(verticalSync); +} + +void Screen::display() { + sf::Event event{}; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + } + } + + std::string title = name + " (" + std::to_string(Time::fps()) + " fps)"; + window.setTitle(title); + + if(renderVideo || makeScreenShoot) + { + sf::Texture copyTexture; + copyTexture.create(window.getSize().x, window.getSize().y); + copyTexture.update(window); + if(makeScreenShoot) + copyTexture.copyToImage().saveToFile("../img/screen.png"); + else + copyTexture.copyToImage().saveToFile("../film/png/" + std::to_string(frame++) + ".png"); + makeScreenShoot = false; + } + + + window.display(); +} + +void Screen::clear() { + window.clear(background); +} + +void Screen::line(const Point4D& p1, const Point4D& p2, sf::Color color) +{ + if (!window.isOpen()) + return; + + sf::Vertex line[] = + { + sf::Vertex(sf::Vector2f(p1.x(), p1.y()), color), + sf::Vertex(sf::Vector2f(p2.x(), p2.y()), color) + }; + window.draw(line, 2, sf::Lines); +} + +void Screen::triangle(const Triangle& triangle) +{ + if(vm == Frame || vm == Borders || vm == Xray || vm == Clipped || vm == Transparency || vm == Normals) { + sf::Vertex lines[4] = + { + sf::Vertex(sf::Vector2f(triangle[0].x(), triangle[0].y()), sf::Color(0, 0, 0, 255)), + sf::Vertex(sf::Vector2f(triangle[1].x(), triangle[1].y()), sf::Color(0, 0, 0, 255)), + sf::Vertex(sf::Vector2f(triangle[2].x(), triangle[2].y()), sf::Color(0, 0, 0, 255)), + sf::Vertex(sf::Vector2f(triangle[0].x(), triangle[0].y()), sf::Color(0, 0, 0, 255)) + }; + + window.draw(lines, 4, sf::LineStrip); + } + if(vm == Frame || vm == Xray) + return; // no texture when we turn on Frame or Xray mode + + sf::Vertex tris[3] = + { + sf::Vertex(sf::Vector2f(triangle[0].x(), triangle[0].y()), triangle.color), + sf::Vertex(sf::Vector2f(triangle[1].x(), triangle[1].y()), triangle.color), + sf::Vertex(sf::Vector2f(triangle[2].x(), triangle[2].y()), triangle.color) + }; + window.draw(tris, 3, sf::Triangles); +} + +void Screen::title(const std::string& title) +{ + name = title; +} + +bool Screen::isOpen() { + return window.isOpen(); +} + +void Screen::close() { + window.close(); +} + +bool Screen::isKeyPressed(sf::Keyboard::Key key) { + return sf::Keyboard::isKeyPressed(key); +} + +Point4D Screen::getMousePosition() const { + sf::Vector2 pos = sf::Mouse::getPosition(window); + return Point4D(pos.x, pos.y, 0, 0); +} + +Point4D Screen::getMouseDisplacement() const { + sf::Vector2 disp = sf::Mouse::getPosition(window) - sf::Vector2(w/2, h/2); + setMouseInCenter(); + return Point4D(disp.x, disp.y, 0, 0); +} + +void Screen::setMouseInCenter() const { + sf::Mouse::setPosition({ w / 2, h / 2 }, window); +} + +void Screen::setMouseCursorVisible(bool visible) { + window.setMouseCursorVisible(visible); +} + +void Screen::keyboardControl() { + // Check all input after this condition please + if (!window.hasFocus()) + return; + + if(isKeyTapped(sf::Keyboard::Num1)) + setMode(ViewMode::Default); + if(isKeyTapped(sf::Keyboard::Num2)) + setMode(ViewMode::Borders); + if(isKeyTapped(sf::Keyboard::Num3)) + setMode(ViewMode::Transparency); + if(isKeyTapped(sf::Keyboard::Num4)) + setMode(ViewMode::Frame); + if(isKeyTapped(sf::Keyboard::Num5)) + setMode(ViewMode::Xray); + if(isKeyTapped(sf::Keyboard::Num6)) + setMode(ViewMode::Clipped); + if(isKeyTapped(sf::Keyboard::Num7)) + setMode(ViewMode::Normals); +} + +bool Screen::isKeyTapped(sf::Keyboard::Key key) { + if (!Screen::isKeyPressed(key)) + return false; + + if(tappedKeys.count(key) == 0) { + tappedKeys.emplace(key, Time::time()); + return true; + } else if((Time::time() - tappedKeys[key]) > 0.2) { + tappedKeys[key] = Time::time(); + return true; + } + return false; +} + +bool Screen::isButtonPressed(sf::Mouse::Button button) { + return sf::Mouse::isButtonPressed(button); +} + +bool Screen::isButtonTapped(sf::Mouse::Button button) { + if (!Screen::isButtonPressed(button)) + return false; + + if(tappedButtons.count(button) == 0) { + tappedButtons.emplace(button, Time::time()); + return true; + } else if((Time::time() - tappedButtons[button]) > 0.2) { + tappedButtons[button] = Time::time(); + return true; + } + return false; +} + + +void Screen::debugText(const std::string& text) { + sf::Text t; + + t.setFont(*ResourceManager::loadFont(font)); + t.setString(text); + t.setCharacterSize(30); + t.setFillColor(sf::Color::Black); + t.setPosition(10, 10); + + window.draw(t); +} + +void Screen::setRender(bool r) { + if(renderVideo && !r) { + std::string c = "ffmpeg -stats -r 60 -i ../film/png/%d.png -vcodec libx264 -crf 1 -pix_fmt yuv420p -frames " + std::to_string(frame) + " ../film/mp4/" + std::to_string(scene) + "_" + name + ".mp4"; + popen(c.c_str(), "w"); + frame = 0; + scene++; + } + renderVideo = r; +} diff --git a/engine/Screen.h b/engine/Screen.h new file mode 100755 index 0000000..8c396cc --- /dev/null +++ b/engine/Screen.h @@ -0,0 +1,92 @@ +// +// Created by Иван Ильин on 14.01.2021. +// + +#ifndef ENGINE_SCREEN_H +#define ENGINE_SCREEN_H + + +#include +#include "Triangle.h" +#include +#include +#include "utils/Time.h" + +class Screen { +public: + enum ViewMode { + Default = 0, + Frame, + Borders, + Xray, + Clipped, + Transparency, + Normals + }; +private: + int w = 1920; + int h = 1080; + + std::string name; + + sf::Color background; + + Screen::ViewMode vm = Screen::ViewMode::Default; + + std::map tappedKeys; + std::map tappedButtons; + + std::string font = "../engine/fonts/Roboto-Thin.ttf"; + + bool renderVideo = false; // performance heavy. I use this to make sequence of .jpg files of screen and then convert this to .mp4 file + int frame = 0; + int scene = 0; // the number of scene + + bool makeScreenShoot = false; +public: + sf::RenderWindow window; + + void open(int screenWidth = 1920, int screenHeight = 1080, const std::string& name = "engine", bool verticalSync = true, sf::Color background = sf::Color(255, 255, 255), sf::Uint32 style = sf::Style::Default); + + void display(); + void clear(); + + void line(const Point4D& p1, const Point4D& p2, sf::Color color = {0, 0, 0}); + void triangle(const Triangle& triangle ); + + void title(const std::string& title); + std::string title() const { return name; }; + + bool isOpen(); + + int width() const {return window.getSize().x;} + int height() const {return window.getSize().y;} + + void close(); + + static bool isKeyPressed(sf::Keyboard::Key key); // returns true if this key is pressed + bool isKeyTapped(sf::Keyboard::Key key); // returns true if this key is tapped and 1/5 sec passed (button bouncing problem solved) + + static bool isButtonPressed(sf::Mouse::Button button); // returns true if this button is pressed + bool isButtonTapped(sf::Mouse::Button button); // returns true if this button is tapped and 1/5 sec passed (button bouncing problem solved) + + Point4D getMousePosition() const; + Point4D getMouseDisplacement() const; + void setMouseInCenter() const; + void setMouseCursorVisible(bool visible); + + void setMode(ViewMode mode) { vm = mode; } + [[nodiscard]] ViewMode mode() const { return vm; } + + void keyboardControl(); + + void debugText(const std::string& text); + + void setRender(bool r); + bool isRender() const { return renderVideo; } + + void makeScreen() { makeScreenShoot = true; } +}; + + +#endif //INC_3DZAVR_SCREEN_H diff --git a/engine/Triangle.cpp b/engine/Triangle.cpp new file mode 100755 index 0000000..52f335e --- /dev/null +++ b/engine/Triangle.cpp @@ -0,0 +1,80 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#include "Triangle.h" + +Triangle::Triangle () { + p[0] = Point4D{0,0,0,1}; + p[1] = Point4D{0,0,0,1}; + p[2] = Point4D{0,0,0,1}; +} + +Triangle::Triangle(const Point4D& p1, const Point4D& p2, const Point4D& p3, double w) { + p[0] = Point4D{p1.x(), p1.y(), p1.z(), w}; + p[1] = Point4D{p2.x(), p2.y(), p2.z(), w}; + p[2] = Point4D{p3.x(), p3.y(), p3.z(), w}; +} + +Triangle Triangle::operator*(const Matrix4x4 &matrix4X4) const { + return Triangle(*this) *= matrix4X4; +} + +Triangle &Triangle::operator*=(const Matrix4x4 &matrix4X4) { + p[0] = matrix4X4 * p[0]; + p[1] = matrix4X4 * p[1]; + p[2] = matrix4X4 * p[2]; + + return *this; +} + +Point4D Triangle::norm() const { + + Point4D v1 = p[1] - p[0]; + Point4D v2 = p[2] - p[0]; + + return v1.cross3D(v2).normalize(); +} + +Point4D Triangle::operator[](int i) const { + return p[i]; +} + +Point4D &Triangle::operator[](int i) { + return p[i]; +} + +Point4D Triangle::pos() const { + return (p[0] + p[1] + p[2])/3.0; +} + +Triangle::Triangle(const Triangle &triangle) { + clip = triangle.clip; + color = triangle.color; + p[0] = triangle[0]; + p[1] = triangle[1]; + p[2] = triangle[2]; +} + +bool Triangle::isPointInside(const Point4D &point) const { + Point4D triangleNorm = norm(); + + double dot1 = (point - p[0]).cross3D(p[1] - p[0]).dot(triangleNorm); + double dot2 = (point - p[1]).cross3D(p[2] - p[1]).dot(triangleNorm); + double dot3 = (point - p[2]).cross3D(p[0] - p[2]).dot(triangleNorm); + + if((dot1 >= 0 && dot2 >= 0 && dot3 >= 0) || (dot1 <= 0 && dot2 <= 0 && dot3 <= 0)) + return true; + return false; +} + +Triangle &Triangle::operator=(const Triangle &triangle) { + if(&triangle != this) { + clip = triangle.clip; + color = triangle.color; + p[0] = triangle[0]; + p[1] = triangle[1]; + p[2] = triangle[2]; + } + return *this; +} diff --git a/engine/Triangle.h b/engine/Triangle.h new file mode 100755 index 0000000..89351a0 --- /dev/null +++ b/engine/Triangle.h @@ -0,0 +1,45 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#ifndef ENGINE_TRIANGLE_H +#define ENGINE_TRIANGLE_H + +#include "utils/Point4D.h" +#include "utils/Matrix4x4.h" +#include + +class Triangle { +public: + // This is for clipping debug: you can distinguish how this triangle was clipped + enum ClipMode { + None = 0, + Cropped, + Doubled, + Skipped + }; + ClipMode clip = None; + sf::Color color; + Point4D p[3]; // points in space + Point4D t[3]; // texture coordinates + + Triangle (); + Triangle (const Triangle& triangle); + Triangle (const Point4D& p1, const Point4D& p2, const Point4D& p3, double w = 1); + Triangle& operator=(const Triangle& triangle); + + [[nodiscard]] Point4D operator[] (int i) const; + [[nodiscard]] Point4D& operator[] (int i); + + [[nodiscard]] Point4D norm() const; + + // Operations with Matrix4x4 + [[nodiscard]] Triangle operator*(const Matrix4x4& matrix4X4) const; + Triangle& operator*=(const Matrix4x4& matrix4X4); + [[nodiscard]] Point4D pos() const; + + [[nodiscard]] bool isPointInside(const Point4D& point) const; +}; + + +#endif //INC_3DZAVR_TRIANGLE_H diff --git a/engine/World.cpp b/engine/World.cpp new file mode 100755 index 0000000..6a31605 --- /dev/null +++ b/engine/World.cpp @@ -0,0 +1,81 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#include +#include +#include "World.h" +#include "utils/Log.h" +#include "Plane.h" + +using namespace std; + +void World::addMesh(const std::shared_ptr& mesh, const string &name) { + _objects.emplace(name, mesh); + Log::log("World::addMesh(): inserted mesh '" + name + "' with " + std::to_string(_objects[name]->triangles().size()) + " tris."); + +} + +void World::loadObj(const string &name, const string &filename,const std::string &materials, const Point4D& scale) { + _objects.emplace(name, std::make_shared(filename, materials, scale)); + Log::log("World::loadObj(): inserted mesh from " + filename + " with name '" + name + "' with " + std::to_string(_objects[name]->triangles().size()) + " tris."); +} + +void World::removeMesh(const string &name) { + _objToRemove.push_back(name); +} + +std::shared_ptr World::operator[](const string &name) { + if(_objects.count(name) == 0) + Log::log("World::operator[]: mesh '" + name + "' does not exist."); + return _objects.find(name)->second; +} + +std::pair World::rayCast(const Point4D& from, const Point4D& to) { + + std::pair result{Point4D{0, 0,0, -1}, ""}; + double minDistance = 10000; + + for(auto& object : _objects) { + if((object.first.find("im") != std::string::npos) || (object.first.find("point") != std::string::npos) || (object.first.find("nr") != std::string::npos)) + continue; + + for(auto& tri : object.second->triangles()) { + Triangle tri_translated(tri[0] + object.second->position(), tri[1] + object.second->position(), tri[2] + object.second->position(), 0); + + Plane plane(tri_translated); + auto intersection = plane.intersection(from, to); + double distance = (intersection.first - from).sqrAbs(); + if(intersection.second > 0 && distance < minDistance && tri_translated.isPointInside(intersection.first)) { + minDistance = distance; + result = {intersection.first, object.first}; + } + } + } + return result; +} + +void World::loadMap(const string &filename, const string &name, const Point4D &scale, const string &materials) { + auto objs = Mesh::LoadObjects(filename, materials, scale); + for(int i = 0; i < objs.size(); i++) { + string meshName = name + "_" + to_string(i); + addMesh(objs[i], meshName); + } +} + +void World::garbageCollector() { + for(auto& obj : _objToRemove) { + if(_objects.erase(obj) > 0) + Log::log("World::garbageCollector(): removed mesh '" + obj + "'"); + else + Log::log("World::garbageCollector(): cannot remove mesh '" + obj + "': mesh does not exist."); + } + _objToRemove.clear(); +} + +void World::removeMeshInstantly(const string &name) { + if(_objects.erase(name) > 0) + Log::log("World::removeMeshInstantly(): removed mesh '" + name + "'"); + else + Log::log("World::removeMeshInstantly(): cannot remove mesh '" + name + "': mesh does not exist."); +} diff --git a/engine/World.h b/engine/World.h new file mode 100755 index 0000000..dab4572 --- /dev/null +++ b/engine/World.h @@ -0,0 +1,38 @@ +// +// Created by Иван Ильин on 13.01.2021. +// + +#ifndef ENGINE_WORLD_H +#define ENGINE_WORLD_H + +#include +#include "Mesh.h" + +class World { +private: + std::map> _objects; + + std::vector _objToRemove; +public: + World() = default; + + [[nodiscard]] std::shared_ptr operator[] (const std::string& name); + + [[nodiscard]] std::map>& objects() { return _objects; } + + void addMesh(const std::shared_ptr& mesh, const std::string& name = ""); + void removeMesh(const std::string& name); + void removeMeshInstantly(const std::string& name); + void garbageCollector(); + void loadObj(const std::string &name, const std::string &filename,const std::string &materials = "", const Point4D& scale = Point4D{1, 1, 1}); + + // rayCast returns pair of Point4D and std::string: + // 1) Point4D is point of collision (the last coordinate is -1 if there are no collisions) + // 2) std::string - name of the object + std::pair rayCast(const Point4D& from, const Point4D& to); + + void loadMap(const std::string& filename, const std::string& name = "", const Point4D& scale = Point4D{1, 1, 1}, const std::string &materials = "../maps/materials.txt"); +}; + + +#endif //INC_3DZAVR_WORLD_H diff --git a/engine/animation/AColor.h b/engine/animation/AColor.h new file mode 100755 index 0000000..6465840 --- /dev/null +++ b/engine/animation/AColor.h @@ -0,0 +1,40 @@ +// +// Created by Иван Ильин on 02.06.2021. +// + +#ifndef ENGINE_ACOLOR_H +#define ENGINE_ACOLOR_H + +#include "Animatable.h" +#include "Animation.h" + +class AColor : public Animation { +private: + sf::Color newColor; + sf::Color startColor; + +public: + AColor(const sf::Color &color, double duration, LoopOut looped, InterpolationType interpolationType) { + _duration = duration; + _looped = looped; + _intType = interpolationType; + _waitFor = true; + + newColor = color; + } + + bool update(Animatable& obj) override { + if(!_started) + startColor = obj.color(); + + Point4D start(startColor.r, startColor.g, startColor.b, startColor.a); + Point4D end(newColor.r, newColor.g, newColor.b, newColor.a); + Point4D mid = start + (end - start)*_p; + + obj.setColor(sf::Color(static_cast(mid.x()), static_cast(mid.y()), static_cast(mid.z()), static_cast(mid.w()))); + + return updateState(); + } +}; + +#endif //SHOOTER_3DZAVR_ACOLOR_H diff --git a/engine/animation/AFunction.h b/engine/animation/AFunction.h new file mode 100755 index 0000000..e81ffd3 --- /dev/null +++ b/engine/animation/AFunction.h @@ -0,0 +1,36 @@ +// +// Created by Иван Ильин on 06.04.2021. +// + +#include + +#include "Animation.h" + +#ifndef ENGINE_AFUNCTION_H +#define ENGINE_AFUNCTION_H + +class AFunction : public Animation { +private: + int _calls = 0; + int _allCalls = 1; + std::function _callBack; + +public: + AFunction(std::function function, int calls, double duration, LoopOut looped, InterpolationType interpolationType) { + _callBack = std::move(function); + _allCalls = calls; + _duration = duration; + _looped = looped; + _intType = interpolationType; + } + + bool update(Animatable& obj) override { + if(_allCalls != 0 && _p >= (double)(_calls+1) / (_allCalls+1)) { + _calls++; + _callBack(); + } + return updateState(); + } +}; + +#endif //MINECRAFT_3DZAVR_AFUNCTION_H diff --git a/engine/animation/ARotate.h b/engine/animation/ARotate.h new file mode 100755 index 0000000..d346330 --- /dev/null +++ b/engine/animation/ARotate.h @@ -0,0 +1,29 @@ +// +// Created by Иван Ильин on 29.01.2021. +// + +#ifndef ENGINE_AROTATE_H +#define ENGINE_AROTATE_H + +#include "Animatable.h" +#include "Animation.h" + +class ARotate : public Animation { +private: + Point4D value; +public: + ARotate(const Point4D& r, double duration, LoopOut looped, InterpolationType interpolationType) { + _duration = duration; + _looped = looped; + _intType = interpolationType; + + value = r; + } + + bool update(Animatable& obj) override { + obj.rotate(value * _dp); + return updateState(); + } +}; + +#endif //INC_3DZAVR_AROTATE_H diff --git a/engine/animation/AScale.h b/engine/animation/AScale.h new file mode 100755 index 0000000..b57ba31 --- /dev/null +++ b/engine/animation/AScale.h @@ -0,0 +1,39 @@ +// +// Created by Иван Ильин on 29.01.2021. +// + +#ifndef ENGINE_ASCALE_H +#define ENGINE_ASCALE_H + +#include "Animatable.h" +#include "Animation.h" + +class AScale : public Animation { +private: + Point4D value; + + std::vector triangles; +public: + AScale(const Point4D &s, double duration, LoopOut looped, InterpolationType interpolationType) { + _duration = duration; + _looped = looped; + _intType = interpolationType; + _waitFor = true; + + value = s; + } + + bool update(Animatable& obj) override { + if(!_started) + triangles = obj.triangles(); + + std::vector newTriangles; + for(auto &t : triangles) { + newTriangles.emplace_back(t * Matrix4x4::Scale(Point4D{1, 1, 1} + (value - Point4D{1, 1, 1}) * _p)); + } + obj.setTriangles(newTriangles); + return updateState(); + } +}; + +#endif //INC_3DZAVR_ASCALE_H diff --git a/engine/animation/ATranslate.h b/engine/animation/ATranslate.h new file mode 100755 index 0000000..4b469c3 --- /dev/null +++ b/engine/animation/ATranslate.h @@ -0,0 +1,33 @@ +// +// Created by Иван Ильин on 29.01.2021. +// + +#ifndef ENGINE_ATRANSLATE_H +#define ENGINE_ATRANSLATE_H + +#include "Animatable.h" +#include "Animation.h" + +class ATranslate : public Animation { +private: + Point4D value; +public: + ATranslate(const Point4D& t, double duration, LoopOut looped, InterpolationType interpolationType) { + _duration = duration; + _looped = looped; + _intType = interpolationType; + + value = t; + } + + bool update(Animatable& obj) override { + obj.translate(value * _dp); + return updateState(); + } + + [[nodiscard]] int type() const override { + return 2; + } +}; + +#endif //INC_3DZAVR_ATRANSLATE_H diff --git a/engine/animation/ATranslateToPoint.h b/engine/animation/ATranslateToPoint.h new file mode 100755 index 0000000..223dafc --- /dev/null +++ b/engine/animation/ATranslateToPoint.h @@ -0,0 +1,34 @@ +// +// Created by Иван Ильин on 29.01.2021. +// + +#ifndef ENGINE_ATRANSLATETOPOINT_H +#define ENGINE_ATRANSLATETOPOINT_H + +#include "Animatable.h" +#include "Animation.h" + +class ATranslateToPoint : public Animation { +private: + Point4D point; + Point4D value; +public: + ATranslateToPoint(const Point4D& p, double duration, LoopOut looped, InterpolationType interpolationType) { + _duration = duration; + _looped = looped; + _intType = interpolationType; + + point = p; + } + + bool update(Animatable& obj) override { + if(!_started) { + value = point - obj.position(); + } + obj.translate(value * _dp); + + return updateState(); + } +}; + +#endif //INC_3DZAVR_ATRANSLATETOPOINT_H diff --git a/engine/animation/AWait.h b/engine/animation/AWait.h new file mode 100755 index 0000000..eab8b17 --- /dev/null +++ b/engine/animation/AWait.h @@ -0,0 +1,30 @@ +// +// Created by Иван Ильин on 29.01.2021. +// + +#ifndef ENGINE_AWAIT_H +#define ENGINE_AWAIT_H + +#include "Animatable.h" +#include "Animation.h" + +class AWait : public Animation { +private: + Point4D value; +public: + explicit AWait(double duration) { + _duration = duration; + _intType = linear; + _waitFor = true; + } + + bool update(Animatable& obj) override { + return updateState(); + } + + [[nodiscard]] int type() const override { + return 1; + } +}; + +#endif //INC_3DZAVR_AWAIT_H diff --git a/engine/animation/Animatable.cpp b/engine/animation/Animatable.cpp new file mode 100755 index 0000000..65fa8f1 --- /dev/null +++ b/engine/animation/Animatable.cpp @@ -0,0 +1,91 @@ +// +// Created by Иван Ильин on 26.01.2021. +// + +#include "Animatable.h" +#include +#include + +#include "ATranslate.h" +#include "ATranslateToPoint.h" +#include "ARotate.h" +#include "AScale.h" +#include "AWait.h" +#include "AFunction.h" +#include "AColor.h" + +void Animatable::a_translate(const std::string& listName, + const Point4D &t, + double duration, + Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new ATranslate(t, duration, looped, interpolationType)); +} + +void Animatable::a_translateToPoint(const std::string& listName, + const Point4D &point, + double duration, + Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new ATranslateToPoint(point, duration, looped, interpolationType)); +} + +void Animatable::a_rotate(const std::string& listName, + const Point4D &r, + double duration, + Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new ARotate(r, duration, looped, interpolationType)); +} + +void Animatable::a_scale(const std::string& listName, + const Point4D &s, + double duration, + Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new AScale(s, duration, looped, interpolationType)); +} + +void Animatable::a_color(const std::string &listName, const sf::Color &color, double duration, Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new AColor(color, duration, looped, interpolationType)); +} + +void Animatable::a_wait(const std::string& listName, double duration) { + animations[listName].emplace_back(new AWait(duration)); +} + +void Animatable::a_function(const std::string &listName, + std::function function, + int calls, + double duration, + Animation::LoopOut looped, + Animation::InterpolationType interpolationType) { + animations[listName].emplace_back(new AFunction(std::move(function), calls, duration, looped, interpolationType)); +} + +void Animatable::a_update() { + + for (auto& [listName, animationList] : animations) { + + if (animationList.empty()) + continue; + auto it = animationList.begin(); + // If it the front animation is 'a_wait()' we should wait until waiting time is over + + if (it.operator*()->waitFor()) { + if (!it.operator*()->update(*this)) + animationList.erase(it); + continue; + } + + + // Otherwise we iterate over all animation until we meet animations.end() or wait animation + while (!animationList.empty() && (it != animationList.end()) && (!it.operator*()->waitFor())) { + if (!it.operator*()->update(*this)) + animationList.erase(it++); + else + it++; + } + } +} diff --git a/engine/animation/Animatable.h b/engine/animation/Animatable.h new file mode 100755 index 0000000..87574f8 --- /dev/null +++ b/engine/animation/Animatable.h @@ -0,0 +1,92 @@ +// +// Created by Иван Ильин on 26.01.2021. +// + +#ifndef ENGINE_ANIMATABLE_H +#define ENGINE_ANIMATABLE_H + +#include +#include "../Triangle.h" + +//class Animation; +#include "Animation.h" +#include +#include + +// All _objects in 3dzavr that should be animated must inherit class Animatable: +class Animatable { +protected: + std::map> animations; + +public: + Animatable() = default; + virtual ~Animatable() = default; + // All methods about animation begins with 'a_' + void a_translate(const std::string& listName, + const Point4D& t, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + void a_translateToPoint(const std::string& listName, + const Point4D& point, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + + void a_rotate(const std::string& listName, + const Point4D& r, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + + void a_scale(const std::string& listName, + const Point4D& s, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + + void a_wait(const std::string& listName, double duration = 1); + + void a_function(const std::string& listName, + std::function function, + int calls = 1, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + + void a_color(const std::string& listName, + const sf::Color& color, + double duration = 1, + Animation::LoopOut looped = Animation::None, + Animation::InterpolationType interpolationType = Animation::bezier); + + + void a_update(); + + void a_stopAllAnimations() { animations.clear(); } + void a_stopAnimationList(const std::string& name) { animations[name].clear(); } + + [[nodiscard]] bool isInAnim() const { + for(auto& animList : animations) + if (!animList.second.empty()) + return true; + return false; + + } + [[nodiscard]] bool isInAnimList(const std::string& name) { return !animations[name].empty(); } + + // methods to override: + // If you want to create new animation you can either add new virtual function here + // or override one of the following function: + [[nodiscard]] virtual Point4D position() const { return Point4D{}; } + [[nodiscard]] virtual Point4D angle() const { return Point4D{}; } + virtual void translate(const Point4D& dv) {} + virtual void rotate(const Point4D& r) {} + [[nodiscard]] virtual std::vector& triangles() { return *(std::vector*)(new std::vector()); } + virtual void setTriangles(const std::vector& tris) {} + + [[nodiscard]] virtual sf::Color color() const {return sf::Color(); } + virtual void setColor(sf::Color c) { } +}; + +#endif //INC_3DZAVR_ANIMATABLE_H diff --git a/engine/animation/Animation.cpp b/engine/animation/Animation.cpp new file mode 100755 index 0000000..1adac2b --- /dev/null +++ b/engine/animation/Animation.cpp @@ -0,0 +1,49 @@ +// +// Created by Иван Ильин on 27.01.2021. +// + +#include "Animation.h" + +#include +#include "../utils/Log.h" + +bool Animation::updateState() { + if(!_started) { + _startAnimationPoint = Time::time(); + _endAnimationPoint = _startAnimationPoint + _duration; + _started = true; + return _duration != 0; + } + + _timeOld = _time; + // linear normalized time: + _time = (Time::time() - _startAnimationPoint)/(_endAnimationPoint - _startAnimationPoint); + + if(_looped != Continue || _time < 0.5) + _dtime = _time - _timeOld; + else { + _time = _timeOld; + //_intType = linear; + } + + switch (_intType) { + case bezier: + _p = Interpolation::Bezier(_bezier[0], _bezier[1], _time); + _dp = Interpolation::dBezier(_bezier[0], _bezier[1], _time, _dtime); + break; + case bouncing: + _p = Interpolation::Bouncing(_time); + _dp = Interpolation::dBouncing(_time, _dtime); + break; + case linear: + _p = Interpolation::Linear(_time); + _dp = Interpolation::dLinear(_time, _dtime); + break; + case cos: + _p = Interpolation::Cos(_time); + _dp = Interpolation::dCos(_time, _dtime); + break; + } + + return (_time < 1) || _looped == Cycle; +} \ No newline at end of file diff --git a/engine/animation/Animation.h b/engine/animation/Animation.h new file mode 100755 index 0000000..0ec26fa --- /dev/null +++ b/engine/animation/Animation.h @@ -0,0 +1,62 @@ +// +// Created by Иван Ильин on 26.01.2021. +// + +#ifndef ENGINE_ANIMATION_H +#define ENGINE_ANIMATION_H + +#include "../utils/Time.h" +#include "../Triangle.h" +#include "Interpolation.h" + +class Animatable; + +class Animation { +public: + enum InterpolationType { + linear, + cos, + bezier, + bouncing + }; + enum LoopOut { + None, + Cycle, + Continue + }; +protected: + double _time = 0; // normalized time (from 0 to 1) + double _dtime = 0; + double _timeOld = 0; + + double _endAnimationPoint = 0; + double _startAnimationPoint = 0; + double _duration = 0; + bool _started = false; + LoopOut _looped = None; + // p - animation progress + double _p = 0; + double _dp = 0; + + InterpolationType _intType = bezier; + Point4D _bezier[2] = {Point4D{0.8, 0}, Point4D{0.2, 1}}; + + // If '_waitFor' == true then we need to finish all animation before starting this one. (for example for a_wait() or a_scale()) + bool _waitFor = false; + + bool updateState(); +public: + Animation() = default; + virtual ~Animation() = default; + + void setBezierParams(const Point4D& p1, const Point4D& p2) { _bezier[0] = p1; _bezier[1] = p2; } + [[nodiscard]] bool waitFor() const { return _waitFor; } + + + // You should override this method for your particular animation + virtual bool update(Animatable& obj) = 0; + + [[nodiscard]] virtual int type() const{return 0;} +}; + +#endif //INC_3DZAVR_ANIMATION_H diff --git a/engine/animation/Interpolation.h b/engine/animation/Interpolation.h new file mode 100755 index 0000000..9d0d135 --- /dev/null +++ b/engine/animation/Interpolation.h @@ -0,0 +1,86 @@ +// +// Created by Иван Ильин on 26.01.2021. +// + +#ifndef ENGINE_INTERPOLATION_H +#define ENGINE_INTERPOLATION_H + +#include "../utils/Point4D.h" +#include + +namespace Interpolation { + static double Linear(double t); + static double Cos(double t); + static double Bezier(const Point4D& p1, const Point4D& p2, double t); + static double Bouncing(double t); + + static double dLinear(double t, double dt); + static double dCos(double t, double dt); + static double dBezier(const Point4D& p1, const Point4D& p2, double t, double dt); + static double dBouncing(double t, double dt); +}; + +double Interpolation::Linear(double t) { + if(t < 0) + t = -t; + return ((int)trunc(t) % 2) ? 1.0 - (t-trunc(t)) : (t-trunc(t)); +} + +double Interpolation::Cos(double t) { + return 0.5*(1 - cos(M_PI*Interpolation::Linear(t))); +} + +double Interpolation::Bezier(const Point4D &p1, const Point4D &p2, double t) { + t = Interpolation::Linear(t); + + double h = 0.000001; + double eps = 0.000001; + + // We are trying to find 's' when px = t + auto f = [=](double s){ + return 3.0*(1.0-s)*(1.0-s)*s*p1.x() + 3.0*(1.0-s)*s*s*p2.x() + s*s*s - t; + }; + // Using found 's' we will calculate resulting py + auto py = [=](double s){ + return 3.0*(1.0-s)*(1.0-s)*s*p1.y() + 3.0*(1.0-s)*s*s*p2.y() + s*s*s; + }; + + auto df = [=](double s){ + return (f(s+h) - f(s-h))/(2.0*h); + }; + + // Newton method + double s1 = 0.0, s2 = 0.5; + int i = 0; + + while(std::abs(s1 - s2) > eps) { + s1 = s2; + s2 = s1 - f(s1) / df(s1); + i++; + } + + return py(s1); +} + +double Interpolation::Bouncing(double t) { + t = Interpolation::Linear(t); + return 0.5*(1.0/(1.0 + exp(10.0*(-4.0*t+0.8))) + (1.0 + 2.5*sin(50.0*(t - 1.0/3.0))*exp(-7.0*t))/(1.0+exp(10.0*(-15.0*t + 3.1)))); +} + +double Interpolation::dLinear(double t, double dt) { + return ((int)trunc(t) % 2) ? -dt : dt; +} + +double Interpolation::dCos(double t, double dt) { + return 0.5*M_PI*sin(M_PI*t)*dt; +} + +double Interpolation::dBezier(const Point4D &p1, const Point4D &p2, double t, double dt) { + return Interpolation::Bezier(p1, p2, t + dt) - Interpolation::Bezier(p1, p2, t); +} + +double Interpolation::dBouncing(double t, double dt) { + return Bouncing(t + dt) - Bouncing(t); +} + +#endif //INC_3DZAVR_INTERPOLATION_H diff --git a/engine/fonts/Roboto-Light.ttf b/engine/fonts/Roboto-Light.ttf new file mode 100755 index 0000000..3526798 Binary files /dev/null and b/engine/fonts/Roboto-Light.ttf differ diff --git a/engine/fonts/Roboto-Medium.ttf b/engine/fonts/Roboto-Medium.ttf new file mode 100755 index 0000000..f714a51 Binary files /dev/null and b/engine/fonts/Roboto-Medium.ttf differ diff --git a/engine/fonts/Roboto-Thin.ttf b/engine/fonts/Roboto-Thin.ttf new file mode 100755 index 0000000..4e797cf Binary files /dev/null and b/engine/fonts/Roboto-Thin.ttf differ diff --git a/engine/fonts/fontRU.ttf b/engine/fonts/fontRU.ttf new file mode 100755 index 0000000..9466430 Binary files /dev/null and b/engine/fonts/fontRU.ttf differ diff --git a/engine/gui/Button.cpp b/engine/gui/Button.cpp new file mode 100755 index 0000000..3548669 --- /dev/null +++ b/engine/gui/Button.cpp @@ -0,0 +1,58 @@ +// +// Created by Иван Ильин on 26.03.2021. +// + +#include "Button.h" +#include "../ResourceManager.h" + +void Button::select() +{ + if (!selected && !pressed) + { + button.setTextureRect(sf::IntRect(selectedState.tx, selectedState.ty, w, h)); + selected = true; + } +} + +void Button::unSelect() +{ + if (selected && !pressed) + { + button.setTextureRect(sf::IntRect(usualState.tx, usualState.ty, w, h)); + selected = false; + } +} + +void Button::press() +{ + if (!pressed) + { + button.setTextureRect(sf::IntRect(pressedState.tx, pressedState.ty, w, h)); + if(checkBox) + pressed = true; + clickSound.play(); + click(); + } + else + { + button.setTextureRect(sf::IntRect(usualState.tx, usualState.ty, w, h)); + if(checkBox) + pressed = false; + } +} + +void Button::init() { + button.setTexture(*ResourceManager::loadTexture(s_texture)); + button.setTextureRect(sf::IntRect(usualState.tx, usualState.ty, w, h)); + button.scale(sx, sy); + button.setPosition(x - w*sx/2, y - h*sy/2); + + text.setFont(*ResourceManager::loadFont(s_font)); + text.setString(s_text); + text.setCharacterSize(h*sy/2); + text.setFillColor(textColor); + text.setPosition(x - text.getLocalBounds().width/2, y - h*sy/2 + text.getLocalBounds().height/4); + + clickSound.setBuffer(*ResourceManager::loadSoundBuffer(s_clickSound)); + clickSound.setVolume(15); +} diff --git a/engine/gui/Button.h b/engine/gui/Button.h new file mode 100755 index 0000000..1422ed1 --- /dev/null +++ b/engine/gui/Button.h @@ -0,0 +1,58 @@ +// +// Created by Иван Ильин on 26.03.2021. +// + +#ifndef ENGINE_BUTTON_H +#define ENGINE_BUTTON_H + +#include +#include +#include + +struct tPos { + int tx; + int ty; +}; + +struct Button +{ + int x; + int y; + + int w; + int h; + + std::function click; + + std::string s_text; + + double sx; + double sy; + + std::string s_texture; + tPos usualState; + tPos selectedState; + tPos pressedState; + + std::string s_font; + sf::Color textColor; + + std::string s_clickSound; + + sf::Sprite button; + sf::Text text; + sf::Sound clickSound; + + bool selected = false; + bool pressed = false; + bool checkBox = false; + + void select(); + void unSelect(); + void press(); + + void init(); +}; + + +#endif //MINECRAFT_3DZAVR_BUTTON_H diff --git a/engine/gui/Window.cpp b/engine/gui/Window.cpp new file mode 100755 index 0000000..6d1925b --- /dev/null +++ b/engine/gui/Window.cpp @@ -0,0 +1,53 @@ +// +// Created by Иван Ильин on 26.03.2021. +// + +#include "Window.h" + +#include +#include "ResourceManager.h" + +void Window::addButton(int x, int y, int w, int h, std::function click, const std::string &text, double sx, double sy, + const std::string &texture, tPos usualState, tPos selectedState, tPos pressedState, + const std::string& font, sf::Color textColor, const std::string& clickSound) { + buttons.push_back(Button{x, y, w, h, std::move(click), text, sx, sy, texture, usualState, selectedState, pressedState, font, textColor, clickSound}); + buttons.back().init(); +} + +void Window::update(const std::shared_ptr& screen) { + + screen->title(s_name); + screen->window.draw(back); + + Point4D mousePos = screen->getMousePosition(); + Point4D dMousePos = mousePos - prevMousePosition; + back.setPosition(back.getPosition() - sf::Vector2f(dMousePos.x()/30, dMousePos.y()/30)); + bool isPressed = screen->isButtonTapped(sf::Mouse::Left); + + for(auto& button : buttons) { + if( mousePos.x() > button.x - button.w*button.sx/2 && mousePos.y() > button.y - button.h*button.sy/2 && + mousePos.x() < button.x + button.w*button.sx/2 && mousePos.y() < button.y + button.h*button.sy/2) { + button.select(); + if(isPressed) + button.press(); + } else { + button.unSelect(); + } + + if(screen->isOpen()) { + screen->window.draw(button.button); + screen->window.draw(button.text); + } + } + + prevMousePosition = mousePos; +} + +void Window::setBackgroundTexture(const std::string &texture, double sx, double sy, int w, int h) { + s_backTexture = texture; + std::shared_ptr t = ResourceManager::loadTexture(s_backTexture); + t->setRepeated(true); + back = sf::Sprite(*t, sf::IntRect(0, 0, w + w/30.0, h + h/30.0)); + back.scale(sx, sy); + back.setPosition(sf::Vector2f(-w/30.0, -h/30.0)); +} diff --git a/engine/gui/Window.h b/engine/gui/Window.h new file mode 100755 index 0000000..8630d11 --- /dev/null +++ b/engine/gui/Window.h @@ -0,0 +1,41 @@ +// +// Created by Иван Ильин on 26.03.2021. +// + +#ifndef ENGINE_WINDOW_H +#define ENGINE_WINDOW_H + +#include +#include + +#include "Button.h" +#include "Screen.h" + +class Window { +private: + std::string s_name; + std::string s_backTexture; + std::vector