diff --git a/examples/Particles/Common.cpp b/examples/Particles/Common.cpp new file mode 100644 index 000000000..5af7225f7 --- /dev/null +++ b/examples/Particles/Common.cpp @@ -0,0 +1,70 @@ +#include "Common.hpp" +#include +#include +#include +#include + +ParticleDemo::ParticleDemo(const Nz::String& name, const ExampleShared& exampleShared) : +m_shared(exampleShared), +m_index(s_demoIndex++), +m_name(name) +{ +} + +void ParticleDemo::Enter(Ndk::StateMachine& fsm) +{ + m_shared.demoName->Update(Nz::SimpleTextDrawer::Draw(Nz::String::Number(m_index+1) + " - " + m_name, 48)); + m_fpsCounter = 0; + m_updateClock.Restart(); + + Ndk::RenderSystem& renderSystem2D = m_shared.world2D->GetSystem(); + Ndk::RenderSystem& renderSystem3D = m_shared.world3D->GetSystem(); + m_oldBackground2D = renderSystem2D.GetDefaultBackground(); + m_oldBackground3D = renderSystem3D.GetDefaultBackground(); +} + +void ParticleDemo::Leave(Ndk::StateMachine& fsm) +{ + m_shared.world2D->GetSystem().SetDefaultBackground(m_oldBackground2D); + m_shared.world3D->GetSystem().SetDefaultBackground(m_oldBackground3D); + + m_entities.clear(); + m_particleGroups.clear(); +} + +bool ParticleDemo::Update(Ndk::StateMachine& fsm, float elapsedTime) +{ + m_fpsCounter++; + if (m_updateClock.GetMilliseconds() > 1000) + { + m_updateClock.Restart(); + + m_shared.fpsCount->Update(Nz::SimpleTextDrawer::Draw(Nz::String::Number(m_fpsCounter) + " FPS", 24)); + m_fpsCounter = 0; + + unsigned int particleCount = 0; + for (const Ndk::EntityHandle& entity : m_particleGroups) + { + const Ndk::ParticleGroupComponent& group = entity->GetComponent(); + particleCount += group.GetParticleCount(); + } + + m_shared.particleCount->Update(Nz::SimpleTextDrawer::Draw(Nz::String::Number(particleCount) + " particles", 36)); + } + + return true; +} + +void ParticleDemo::RegisterEntity(const Ndk::EntityHandle& entity) +{ + m_entities.emplace_back(entity); +} + +void ParticleDemo::RegisterParticleGroup(const Ndk::EntityHandle& entity) +{ + NazaraAssert(entity->HasComponent(), "Must have particle group component"); + + m_particleGroups.emplace_back(entity); +} + +std::size_t ParticleDemo::s_demoIndex = 0; diff --git a/examples/Particles/Common.hpp b/examples/Particles/Common.hpp new file mode 100644 index 000000000..658b97419 --- /dev/null +++ b/examples/Particles/Common.hpp @@ -0,0 +1,64 @@ +#pragma once + +#ifndef NAZARA_EXAMPLES_PARTICLES_COMMON_HPP +#define NAZARA_EXAMPLES_PARTICLES_COMMON_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ParticleDemo; + +struct ExampleShared +{ + mutable std::mt19937 randomGen; + std::vector> demos; + Nz::RenderWindow* target; + Nz::TextSpriteRef demoName; + Nz::TextSpriteRef fpsCount; + Nz::TextSpriteRef particleCount; + Ndk::EntityHandle viewer2D; + Ndk::EntityHandle viewer3D; + Ndk::WorldHandle world2D; + Ndk::WorldHandle world3D; +}; + +class ParticleDemo : public Ndk::State +{ + public: + ParticleDemo(const Nz::String& name, const ExampleShared& exampleShared); + ~ParticleDemo() = default; + + void Enter(Ndk::StateMachine& fsm) override; + void Leave(Ndk::StateMachine& fsm) override; + + bool Update(Ndk::StateMachine& fsm, float elapsedTime) override; + + protected: + const ExampleShared& m_shared; + + void RegisterEntity(const Ndk::EntityHandle& entity); + void RegisterParticleGroup(const Ndk::EntityHandle& entity); + + private: + std::size_t m_index; + std::vector m_entities; + std::vector m_particleGroups; + Nz::BackgroundRef m_oldBackground2D; + Nz::BackgroundRef m_oldBackground3D; + Nz::Clock m_updateClock; + Nz::String m_name; + unsigned int m_fpsCounter; + + static std::size_t s_demoIndex; +}; + +#endif // NAZARA_EXAMPLES_PARTICLES_COMMON_HPP diff --git a/examples/Particles/LogoDemo.cpp b/examples/Particles/LogoDemo.cpp new file mode 100644 index 000000000..cc0c2a610 --- /dev/null +++ b/examples/Particles/LogoDemo.cpp @@ -0,0 +1,170 @@ +#include "LogoDemo.hpp" +#include +#include +#include +#include +#include + +namespace +{ + const float duration = 10.f; + const float maxVel = 50.f; + const float pauseTime = 3.f; + const float startTime = 2.f; + const float speed = 3.f; +} + +struct SpriteController : public Nz::ParticleController +{ + void Apply(Nz::ParticleGroup& system, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) override + { + if (!enabled) + return; + + auto posPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto velPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); + + for (unsigned int i = startId; i <= endId; ++i) + posPtr[i] += velPtr[i] * elapsedTime * factor; + } + + bool enabled = false; + float factor = 1.f; +}; + + +class SpriteRenderer : public Nz::ParticleRenderer +{ + public: + SpriteRenderer(Nz::MaterialRef mat) : + m_material(mat) + { + } + + void Render(const Nz::ParticleGroup& system, const Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, Nz::AbstractRenderQueue* renderQueue) + { + Nz::Vector2f size(1.f, 1.f); + Nz::SparsePtr sizePtr(&size, 0); + Nz::SparsePtr sinCosPtr(nullptr, 0); + + renderQueue->AddBillboards(0, m_material, endId - startId + 1, mapper.GetComponentPtr(Nz::ParticleComponent_Position), sizePtr, sinCosPtr, mapper.GetComponentPtr(Nz::ParticleComponent_Color)); + } + + private: + Nz::MaterialRef m_material; +}; + +LogoExample::LogoExample(ExampleShared& sharedData) : +ParticleDemo("Logo", sharedData) +{ + Nz::ImageParams params; + params.loadFormat = Nz::PixelFormatType_RGBA8; + + if (!m_logo.LoadFromFile("resources/Logo.png", params)) + NazaraError("Failed to load logo!"); + + unsigned int width = m_logo.GetWidth(); + unsigned int height = m_logo.GetHeight(); + m_pixels.reserve(width * height); + + for (unsigned int y = 0; y < height; ++y) + { + for (unsigned int x = 0; x < width; ++x) + { + Nz::Color color = m_logo.GetPixelColor(x, y); + if (color.a == 0) + continue; + + PixelData data; + data.pos.Set(x, y); + data.color = color; + + m_pixels.push_back(data); + } + } + + + Nz::MaterialRef material = Nz::Material::New(); + material->EnableBlending(true); + material->EnableDepthWrite(false); + material->EnableFaceCulling(false); + material->SetDstBlend(Nz::BlendFunc_InvSrcAlpha); + material->SetSrcBlend(Nz::BlendFunc_SrcAlpha); + + m_controller = new SpriteController; + m_renderer = new SpriteRenderer(std::move(material)); +} + +void LogoExample::Enter(Ndk::StateMachine& fsm) +{ + ParticleDemo::Enter(fsm); + + m_shared.world3D->GetSystem().SetDefaultBackground(nullptr); + + Nz::TextureRef backgroundTexture = Nz::Texture::New(); + if (backgroundTexture->LoadFromFile("resources/stars-background.jpg")) + m_shared.world2D->GetSystem().SetDefaultBackground(Nz::TextureBackground::New(std::move(backgroundTexture))); + + Ndk::EntityHandle particleGroupEntity = m_shared.world2D->CreateEntity(); + Ndk::ParticleGroupComponent& particleGroup = particleGroupEntity->AddComponent(m_pixels.size(), Nz::ParticleLayout_Sprite); + RegisterParticleGroup(particleGroupEntity); + + particleGroup.AddController(m_controller); + particleGroup.SetRenderer(m_renderer); + + m_particles = static_cast(particleGroup.CreateParticles(m_pixels.size())); + ResetParticles(-duration * (speed / 2.f)); + + m_accumulator = pauseTime + duration; + m_totalAccumulator = 0.f; +} + +void LogoExample::Leave(Ndk::StateMachine & fsm) +{ + ParticleDemo::Leave(fsm); +} + +bool LogoExample::Update(Ndk::StateMachine& fsm, float elapsedTime) +{ + if (!ParticleDemo::Update(fsm, elapsedTime)) + return false; + + m_totalAccumulator += elapsedTime; + if (m_totalAccumulator <= startTime) + return true; + + m_accumulator += elapsedTime; + + SpriteController* controller = static_cast(m_controller.Get()); + if (m_accumulator > pauseTime + 2.f * duration) + { + ResetParticles(0.f); + m_accumulator = 0.f; + } + + controller->enabled = (m_accumulator > pauseTime); + controller->factor = -speed + speed * (m_accumulator - pauseTime) / (duration); + + return true; +} + +void LogoExample::ResetParticles(float elapsed) +{ + Nz::Vector2f center = {m_shared.target->GetWidth() / 2.f, m_shared.target->GetHeight() / 2.f}; + Nz::Vector2f offset = center - Nz::Vector2f(Nz::Vector2ui(m_logo.GetSize()) / 2); + + float ratio = float(m_shared.target->GetWidth()) / m_shared.target->GetHeight(); + std::uniform_real_distribution disX(-maxVel * ratio, maxVel * ratio); + std::uniform_real_distribution disY(-maxVel, maxVel); + + Nz::ParticleStruct_Sprite* sprite = m_particles; + for (PixelData& data : m_pixels) + { + sprite->color = data.color; + sprite->position = offset + Nz::Vector2f(data.pos); + sprite->rotation = 0.f; + sprite->velocity.Set(disX(m_shared.randomGen), disY(m_shared.randomGen), 0.f); + sprite->position += sprite->velocity * elapsed; + sprite++; + } +} diff --git a/examples/Particles/LogoDemo.hpp b/examples/Particles/LogoDemo.hpp new file mode 100644 index 000000000..d5c374d32 --- /dev/null +++ b/examples/Particles/LogoDemo.hpp @@ -0,0 +1,41 @@ +#pragma once + +#ifndef NAZARA_EXAMPLES_PARTICLES_LOGO_HPP +#define NAZARA_EXAMPLES_PARTICLES_LOGO_HPP + +#include +#include +#include +#include +#include "Common.hpp" + +class LogoExample : public ParticleDemo +{ + public: + LogoExample(ExampleShared& sharedData); + ~LogoExample() = default; + + void Enter(Ndk::StateMachine& fsm) override; + void Leave(Ndk::StateMachine& fsm) override; + bool Update(Ndk::StateMachine& fsm, float elapsedTime) override; + + private: + void ResetParticles(float elapsed); + + struct PixelData + { + Nz::Vector2ui pos; + Nz::Color color; + }; + + std::vector m_pixels; + Nz::BackgroundRef m_oldBackground; + Nz::ParticleStruct_Sprite* m_particles; + Nz::Image m_logo; + Nz::ParticleControllerRef m_controller; + Nz::ParticleRendererRef m_renderer; + float m_accumulator; + float m_totalAccumulator; +}; + +#endif // NAZARA_EXAMPLES_PARTICLES_LOGO_HPP diff --git a/examples/Particles/SpacebattleDemo.cpp b/examples/Particles/SpacebattleDemo.cpp new file mode 100644 index 000000000..8cc1558c6 --- /dev/null +++ b/examples/Particles/SpacebattleDemo.cpp @@ -0,0 +1,826 @@ +#include "SpacebattleDemo.hpp" +#include +#include +#include +#include +#include +#include + +namespace +{ + const float maxLaserLife = 15.f; + const float maxSmokeLife = 20.f; +} + +struct SpaceshipComponent : public Ndk::Component +{ + SpaceshipComponent() + { + engineSound.SetBuffer(Nz::SoundBufferManager::Get("resources/spaceship_loop.wav")); + engineSound.EnableSpatialization(true); + engineSound.SetMinDistance(10.f); + engineSound.SetPitch(1.5f); + + hitSound.SetBuffer(Nz::SoundBufferManager::Get("resources/explosion.wav")); + hitSound.EnableSpatialization(true); + hitSound.SetMinDistance(150.f); + + laserSound.SetBuffer(Nz::SoundBufferManager::Get("resources/laser.wav")); + laserSound.EnableSpatialization(true); + laserSound.SetMinDistance(150.f); + laserSound.SetVolume(60.f); + } + + std::array laserBeamSprites; + Nz::Sound engineSound; + Nz::Sound hitSound; + Nz::Sound laserSound; + Nz::UInt64 hitTime = 0; + Nz::Vector3f targetPos = Nz::Vector3f::Zero(); + bool attacking = true; + + static Ndk::ComponentIndex componentIndex; +}; +Ndk::ComponentIndex SpaceshipComponent::componentIndex; + +struct LaserBeamComponent : public Ndk::Component +{ + LaserBeamComponent() + { + Nz::MaterialRef laserBeamMaterial = Nz::MaterialLibrary::Get("LaserBeam"); + for (Nz::Sprite& sprite : sprites) + { + sprite.SetMaterial(laserBeamMaterial); + sprite.SetOrigin(Nz::Vector2f(0.f, 0.5f)); + sprite.SetTextureCoords(Nz::Rectf(0.f, 0.f, 50.f, 1.f)); + } + } + + void OnAttached() override + { + auto& spaceshipCom = m_entity->GetComponent(); + spaceshipCom.laserSound.Play(); + } + + std::array sprites; + Nz::Vector3f origin = Nz::Vector3f::Zero(); + float length = 1500.f; + float life = 2.f; + float width = 2.f; + + static Ndk::ComponentIndex componentIndex; +}; +Ndk::ComponentIndex LaserBeamComponent::componentIndex; + +class LaserBeamSystem : public Ndk::System +{ + public: + LaserBeamSystem(const ExampleShared& sharedData) : + m_sharedData(sharedData) + { + Requires(); + } + + void OnEntityAdded(Ndk::Entity* entity) override + { + auto& laserComponent = entity->GetComponent(); + auto& gfxComponent = entity->GetComponent(); + + for (Nz::Sprite& sprite : laserComponent.sprites) + sprite.SetSize({laserComponent.length, laserComponent.width}); + + gfxComponent.Attach(&laserComponent.sprites[0], Nz::Matrix4f::Transform(laserComponent.origin, Nz::EulerAnglesf(0.f, 90.f, 0.f))); + gfxComponent.Attach(&laserComponent.sprites[1], Nz::Matrix4f::Transform(laserComponent.origin, Nz::EulerAnglesf(90.f, 90.f, 0.f))); + } + + void OnUpdate(float elapsedTime) override + { + const float scrollSpeed = 2.f; + for (const Ndk::EntityHandle& entity : GetEntities()) + { + auto& laserComponent = entity->GetComponent(); + for (Nz::Sprite& sprite : laserComponent.sprites) + { + Nz::Rectf rect = sprite.GetTextureCoords(); + rect.x = std::fmod(rect.x - elapsedTime * scrollSpeed, rect.width); + + sprite.SetTextureCoords(rect); + } + } + } + + static Ndk::SystemIndex systemIndex; + + private: + const ExampleShared& m_sharedData; +}; +Ndk::SystemIndex LaserBeamSystem::systemIndex; + +class SpaceshipSystem : public Ndk::System +{ + public: + SpaceshipSystem(const ExampleShared& sharedData) : + m_sharedData(sharedData) + { + Requires(); + } + + void OnEntityAdded(Ndk::Entity* entity) override + { + std::uniform_real_distribution pitchDis(0.8f, 1.5f); + + auto& nodeComponent = entity->GetComponent(); + auto& spaceshipComponent = entity->GetComponent(); + + spaceshipComponent.engineSound.SetPosition(nodeComponent.GetPosition()); + spaceshipComponent.engineSound.Play(); + spaceshipComponent.engineSound.EnableLooping(true); + + spaceshipComponent.laserSound.SetPitch(pitchDis(m_sharedData.randomGen)); + } + + void OnUpdate(float elapsedTime) override + { + const float escapeMaxDist = 50.f; + const float speed = 200.f; + + Nz::UInt64 curTime = Nz::GetElapsedMilliseconds(); + std::uniform_real_distribution dis(-escapeMaxDist, escapeMaxDist); + + for (const Ndk::EntityHandle& entity : GetEntities()) + { + auto& nodeComponent = entity->GetComponent(); + auto& spaceshipComponent = entity->GetComponent(); + auto& velocityComponent = entity->GetComponent(); + + //< I agree, I need some kind of SoundEmitterComponent + spaceshipComponent.engineSound.SetPosition(nodeComponent.GetPosition()); + spaceshipComponent.engineSound.SetVelocity(velocityComponent.linearVelocity); + + spaceshipComponent.hitSound.SetPosition(nodeComponent.GetPosition()); + spaceshipComponent.hitSound.SetVelocity(velocityComponent.linearVelocity); + + spaceshipComponent.laserSound.SetPosition(nodeComponent.GetPosition()); + spaceshipComponent.laserSound.SetVelocity(velocityComponent.linearVelocity); + + Nz::Vector3f targetDir = spaceshipComponent.targetPos - nodeComponent.GetPosition(); + targetDir.Normalize(); + + Nz::Quaternionf targetRotation = Nz::Quaternionf::RotationBetween(Nz::Vector3f::Forward(), targetDir); + + nodeComponent.SetRotation(Nz::Quaternionf::Slerp(nodeComponent.GetRotation(), targetRotation, elapsedTime * 1.5f)); + + Nz::Vector3f actualDir = nodeComponent.GetForward(); + float sqDistance = spaceshipComponent.targetPos.SquaredDistance(nodeComponent.GetPosition()); + + if (spaceshipComponent.attacking) + { + float dotProduct = targetDir.DotProduct(actualDir); + if (dotProduct > 0.9f && sqDistance < (150.f * 150.f) && !entity->HasComponent()) + { + auto& laserBeam = entity->AddComponent(); + laserBeam.origin = Nz::Vector3f::Forward() * 12.f + Nz::Vector3f::Down() * 2.f; + } + + if (sqDistance < (100.f * 100.f)) + { + entity->RemoveComponent(); + + spaceshipComponent.targetPos -= Nz::Vector3f(dis(m_sharedData.randomGen), dis(m_sharedData.randomGen), dis(m_sharedData.randomGen)) * -actualDir * escapeMaxDist / 2.f; + spaceshipComponent.attacking = false; + } + } + else if (sqDistance < (50.f * 50.f) && spaceshipComponent.hitTime == 0) + { + spaceshipComponent.targetPos = Nz::Vector3f::Zero(); + spaceshipComponent.attacking = true; + } + + if (spaceshipComponent.hitTime == 0 || curTime - spaceshipComponent.hitTime <= 1000) + velocityComponent.linearVelocity = actualDir * speed; + else if (curTime - spaceshipComponent.hitTime > 10000) + entity->Kill(); + } + } + + static Ndk::SystemIndex systemIndex; + + private: + const ExampleShared& m_sharedData; +}; +Ndk::SystemIndex SpaceshipSystem::systemIndex; + +struct TorpedoParticle +{ + Nz::Color color; + Nz::Vector2f size; + Nz::Vector3f position; + Nz::Vector3f velocity; + float rotation; + float life; +}; + +SpacebattleExample::SpacebattleExample(ExampleShared& sharedData) : +ParticleDemo("Space battle", sharedData) +{ + Ndk::InitializeComponent("Lasrbeam"); + Ndk::InitializeComponent("Spceship"); + Ndk::InitializeSystem(); + Ndk::InitializeSystem(); + + Nz::ModelParameters parameters; + parameters.mesh.optimizeIndexBuffers = false; + + Nz::Color grey(100, 100, 100); + + if (!m_turret.baseModel.LoadFromFile("resources/Turret/base.obj", parameters)) + NazaraWarning("Failed to load base.obj"); + + for (unsigned int i = 0; i < m_turret.baseModel.GetMaterialCount(); ++i) + m_turret.baseModel.GetMaterial(i)->SetDiffuseColor(grey); + + if (!m_turret.rotatingBaseModel.LoadFromFile("resources/Turret/rotating_base.obj", parameters)) + NazaraWarning("Failed to load rotating_base.obj"); + + for (unsigned int i = 0; i < m_turret.rotatingBaseModel.GetMaterialCount(); ++i) + m_turret.rotatingBaseModel.GetMaterial(i)->SetDiffuseColor(grey); + + if (!m_turret.cannonBaseModel.LoadFromFile("resources/Turret/cannon_base.obj", parameters)) + NazaraWarning("Failed to load cannon_base.obj"); + + for (unsigned int i = 0; i < m_turret.cannonBaseModel.GetMaterialCount(); ++i) + m_turret.cannonBaseModel.GetMaterial(i)->SetDiffuseColor(grey); + + parameters.mesh.texCoordScale.Set(40.f, 40.f); + parameters.mesh.matrix = Nz::Matrix4f::Rotate(Nz::EulerAnglesf(0.f, 180.f, 0.f)); + if (!m_turret.cannonModel.LoadFromFile("resources/Turret/cannon.obj", parameters)) + NazaraWarning("Failed to load cannon.obj"); + + // Since OBJ don't support normal maps.. + m_turret.cannonModel.GetMaterial(0)->SetNormalMap("resources/Turret/198_norm.jpg"); + + parameters.mesh.matrix.MakeIdentity(); + parameters.mesh.texCoordScale.Set(1.f, 1.f); + + parameters.mesh.center = true; + if (!m_spacestationModel.LoadFromFile("resources/SpaceStation/space_station.obj", parameters)) + NazaraWarning("Failed to load space_station.obj"); + + parameters.mesh.texCoordScale.Set(1.f, -1.f); + parameters.mesh.matrix.MakeRotation(Nz::EulerAnglesf(0.f, -90.f, 0.f)); + + if (!m_spaceshipModel.LoadFromFile("resources/space_frigate_6/space_frigate_6.obj", parameters)) + NazaraWarning("Failed to load space_frigate_6.obj"); + + // Since OBJ don't support normal maps.. + for (unsigned int i = 0; i < m_spaceshipModel.GetMaterialCount(); ++i) + { + m_spaceshipModel.GetMaterial(i)->SetEmissiveMap("resources/space_frigate_6/space_frigate_6_illumination.jpg"); + m_spaceshipModel.GetMaterial(i)->SetNormalMap("resources/space_frigate_6/space_frigate_6_normal.png"); + } + + Nz::TextureRef skyboxCubemap = Nz::Texture::New(); + if (skyboxCubemap->Create(Nz::ImageType_Cubemap, Nz::PixelFormatType_RGBA8, 2048, 2048)) + { + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_PositiveX, "resources/purple_nebula_skybox/purple_nebula_skybox_right1.png"); + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_PositiveY, "resources/purple_nebula_skybox/purple_nebula_skybox_top3.png"); + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_PositiveZ, "resources/purple_nebula_skybox/purple_nebula_skybox_front5.png"); + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_NegativeX, "resources/purple_nebula_skybox/purple_nebula_skybox_left2.png"); + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_NegativeY, "resources/purple_nebula_skybox/purple_nebula_skybox_bottom4.png"); + skyboxCubemap->LoadFaceFromFile(Nz::CubemapFace_NegativeZ, "resources/purple_nebula_skybox/purple_nebula_skybox_back6.png"); + + m_skybox.SetTexture(std::move(skyboxCubemap)); + } + + m_torpedoDeclaration = Nz::ParticleDeclaration::New(); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Color, Nz::ComponentType_Color, NazaraOffsetOf(TorpedoParticle, color)); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Position, Nz::ComponentType_Float3, NazaraOffsetOf(TorpedoParticle, position)); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Rotation, Nz::ComponentType_Float1, NazaraOffsetOf(TorpedoParticle, rotation)); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Size, Nz::ComponentType_Float2, NazaraOffsetOf(TorpedoParticle, size)); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Life, Nz::ComponentType_Float1, NazaraOffsetOf(TorpedoParticle, life)); + m_torpedoDeclaration->EnableComponent(Nz::ParticleComponent_Velocity, Nz::ComponentType_Float3, NazaraOffsetOf(TorpedoParticle, velocity)); + + Nz::TextureSampler diffuseSampler; + diffuseSampler.SetWrapMode(Nz::SamplerWrap_Repeat); + + Nz::MaterialRef material = Nz::Material::New("Translucent3D"); + material->SetDiffuseMap("resources/LaserBeam.png"); + material->SetDiffuseSampler(diffuseSampler); + + Nz::MaterialLibrary::Register("LaserBeam", std::move(material)); + + Nz::MaterialRef sparkleMat1 = Nz::Material::New("Translucent3D"); + + sparkleMat1->SetDiffuseMap("resources/flare1.png"); + Nz::MaterialLibrary::Register("TorpedoFlare1", std::move(sparkleMat1)); + + m_spaceshipTemplate = m_shared.world3D->CreateEntity(); + m_spaceshipTemplate->Enable(false); + + auto& gfxComponent = m_spaceshipTemplate->AddComponent(); + auto& nodeComponent = m_spaceshipTemplate->AddComponent(); + auto& velocityComponent = m_spaceshipTemplate->AddComponent(); + auto& spaceshipComponent = m_spaceshipTemplate->AddComponent(); + gfxComponent.Attach(&m_spaceshipModel); + + m_ambientMusic.OpenFromFile("resources/ambience.ogg"); + m_ambientMusic.SetVolume(60.f); +} + +void SpacebattleExample::Enter(Ndk::StateMachine& fsm) +{ + ParticleDemo::Enter(fsm); + + m_shared.world3D->AddSystem(m_shared); + m_shared.world3D->AddSystem(m_shared); + + Ndk::RenderSystem& renderSystem2D = m_shared.world2D->GetSystem(); + Ndk::RenderSystem& renderSystem3D = m_shared.world3D->GetSystem(); + renderSystem2D.SetDefaultBackground(nullptr); + renderSystem3D.SetDefaultBackground(&m_skybox); + + CreateSpaceShip(); + CreateTurret(); + + Ndk::EntityHandle light = m_shared.world3D->CreateEntity(); + Ndk::NodeComponent& lightNode = light->AddComponent(); + Ndk::LightComponent& lightComp = light->AddComponent(Nz::LightType_Directional); + lightNode.SetRotation(Nz::EulerAnglesf(-30.f, 0.f, 0.f)); + RegisterEntity(light); + + Ndk::NodeComponent& cameraNode = m_shared.viewer3D->GetComponent(); + cameraNode.SetParent(m_turret.cannonAnchorEntity); + cameraNode.SetPosition(Nz::Vector3f::Up() * 4.f - Nz::Vector3f::Backward() * 6.f); + cameraNode.SetRotation(Nz::EulerAnglesf(0.f, 180.f, 0.f)); + + m_introTimer = 10.f; + m_spaceshipSpawnCounter = -5.f; + m_turretBaseRotation = 0.f; + m_turretCannonBaseRotation = 0.f; + m_turretShootTimer = 0.f; + + Ndk::EntityHandle torpedoGroupEntity = m_shared.world3D->CreateEntity(); + m_torpedoGroup = torpedoGroupEntity->AddComponent(200, m_torpedoDeclaration).CreateHandle(); + RegisterParticleGroup(torpedoGroupEntity); + + m_torpedoGroup->AddController(Nz::ParticleFunctionController::New([] (Nz::ParticleGroup& group, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) + { + auto positionPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto lifePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Life); + auto rotationPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Rotation); + auto velocityPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); + + for (unsigned int i = startId; i <= endId; ++i) + { + rotationPtr[i] += elapsedTime * 90.f; + positionPtr[i] += velocityPtr[i] * elapsedTime; + + lifePtr[i] -= elapsedTime; + if (lifePtr[i] < 0.f) + group.KillParticle(i); + } + })); + + + m_torpedoGroup->AddController(Nz::ParticleFunctionController::New([this] (Nz::ParticleGroup& group, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) + { + auto positionPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto rotationPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Rotation); + auto sizePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Size); + auto velocityPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); + + auto& spaceshipSystem = m_shared.world3D->GetSystem(); + + for (unsigned int i = startId; i <= endId; ++i) + { + Nz::Spheref torpedoSphere(positionPtr[i], std::max(sizePtr[i].x, sizePtr[i].y) * 0.1f); + + for (const Ndk::EntityHandle& entity : spaceshipSystem.GetEntities()) + { + auto& spaceshipNode = entity->GetComponent(); + + Nz::Spheref spaceshipSphere(spaceshipNode.GetPosition(), 10.f); + if (torpedoSphere.Intersect(spaceshipSphere)) + { + entity->RemoveComponent(); + + group.KillParticle(i); + + const float hitMaxDist = 500.f; + + std::uniform_real_distribution dis(-hitMaxDist, hitMaxDist); + + auto& spaceshipComponent = entity->GetComponent(); + spaceshipComponent.attacking = false; + spaceshipComponent.engineSound.Stop(); + spaceshipComponent.hitSound.Play(); + spaceshipComponent.hitTime = Nz::GetElapsedMilliseconds(); + spaceshipComponent.targetPos = Nz::Vector3f(dis(m_shared.randomGen), dis(m_shared.randomGen), dis(m_shared.randomGen)); + + auto& emitter = entity->AddComponent(); + emitter.SetEmissionCount(2); + emitter.SetEmissionRate(200.f); + + emitter.SetSetupFunc([this] (const Ndk::EntityHandle& entity, Nz::ParticleMapper& mapper, unsigned int count) + { + auto& gen = m_shared.randomGen; + + const float maxFireVel = 15.f; + std::uniform_real_distribution lifeDis(-0.5f, 0.5f); + std::uniform_real_distribution normalDis(-1.f, 1.f); + std::uniform_real_distribution posDis(-0.1f, 0.1f); + std::uniform_real_distribution rotDis(-180.f, 180.f); + std::uniform_real_distribution sizeDis(1.0f, 4.f); + std::uniform_real_distribution velDis(-maxFireVel, maxFireVel); + + Nz::Vector3f pos = entity->GetComponent().GetPosition(); + + Nz::ParticleStruct_Billboard* billboards = static_cast(mapper.GetPointer()); + Nz::ParticleStruct_Billboard* smokeParticles = static_cast(m_smokeGroup->CreateParticles(count)); + for (unsigned int i = 0; i < count; ++i) + { + billboards[i].color = Nz::Color::White; + billboards[i].life = 1.f + lifeDis(gen); + billboards[i].position = pos + Nz::Vector3f(posDis(gen), posDis(gen), posDis(gen)); + billboards[i].rotation = rotDis(gen); + billboards[i].size = {1.28f, 1.28f}; + billboards[i].size *= sizeDis(gen); + billboards[i].velocity.Set(normalDis(gen), normalDis(gen), normalDis(gen)); + billboards[i].velocity.Normalize(); + billboards[i].velocity *= velDis(gen); + + smokeParticles[i].color = Nz::Color(128, 128, 128, 0); + smokeParticles[i].life = maxSmokeLife; + smokeParticles[i].position = billboards[i].position; + smokeParticles[i].rotation = billboards[i].rotation; + smokeParticles[i].size = {2.56f, 2.56f}; + smokeParticles[i].size *= sizeDis(gen); + smokeParticles[i].velocity = billboards[i].velocity / 2.f; + } + }); + m_fireGroup->AddEmitter(entity); + + break; + } + } + } + })); + + m_torpedoGroup->SetRenderer(Nz::ParticleFunctionRenderer::New([sparkleMat1 = Nz::MaterialLibrary::Get("TorpedoFlare1")] (const Nz::ParticleGroup& group, const Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, Nz::AbstractRenderQueue* renderQueue) + { + auto positionPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto rotationPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Rotation); + auto sizePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Size); + auto velocityPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); + + renderQueue->AddBillboards(0, sparkleMat1, endId - startId + 1, positionPtr, sizePtr, rotationPtr); + for (unsigned int i = startId; i <= endId; ++i) + { + Nz::AbstractRenderQueue::PointLight pointLight; + pointLight.ambientFactor = 0.f; + pointLight.attenuation = 0.9f; + pointLight.color = Nz::Color::Cyan; + pointLight.diffuseFactor = 1.f; + pointLight.position = positionPtr[i]; + pointLight.radius = std::max(sizePtr[i].x, sizePtr[i].y) * 2.f; + pointLight.invRadius = 1.f / pointLight.radius; + pointLight.shadowMap = nullptr; + + renderQueue->AddPointLight(pointLight); + } + })); + + + ////////////////////////////////////////////////////////////////////////// + + Ndk::EntityHandle fireGroupEntity = m_shared.world3D->CreateEntity(); + m_fireGroup = fireGroupEntity->AddComponent(40000, Nz::ParticleDeclaration::Get(Nz::ParticleLayout_Billboard)).CreateHandle(); + RegisterParticleGroup(fireGroupEntity); + + Ndk::EntityHandle smokeGroupEntity = m_shared.world3D->CreateEntity(); + m_smokeGroup = smokeGroupEntity->AddComponent(40000, Nz::ParticleDeclaration::Get(Nz::ParticleLayout_Billboard)).CreateHandle(); + RegisterParticleGroup(smokeGroupEntity); + + auto movementController = Nz::ParticleFunctionController::New([this] (Nz::ParticleGroup& group, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) + { + auto lifePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Life); + auto posPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto sizePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Size); + auto velPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); + + auto& spaceshipSystem = m_shared.world3D->GetSystem(); + + const Nz::Vector2f sizeGrowth(0.5f); + + float velFactor = std::pow(0.9f, elapsedTime * 15.f); + for (unsigned int i = startId; i <= endId; ++i) + { + float& remainingLife = lifePtr[i]; + + remainingLife -= elapsedTime; + if (remainingLife <= 0.f) + { + group.KillParticle(i); + continue; + } + + Nz::Vector3f& position = posPtr[i]; + Nz::Vector2f& size = sizePtr[i]; + Nz::Vector3f& velocity = velPtr[i]; + + position += velPtr[i] * elapsedTime; + size += sizeGrowth * elapsedTime; + velocity *= (velocity.GetSquaredLength() >= 1.f) ? velFactor : 1.f; + + if (remainingLife <= 18.f) + { + for (const Ndk::EntityHandle& entity : spaceshipSystem.GetEntities()) + { + auto& spaceshipNode = entity->GetComponent(); + + Nz::Spheref spaceshipSphere(spaceshipNode.GetPosition(), 5.f); + if (spaceshipSphere.Contains(position)) + { + auto& spaceshipVel = entity->GetComponent(); + + Nz::Vector3f force = spaceshipVel.linearVelocity * 2.f + (position - spaceshipSphere.GetPosition()) * 10.f; + velocity += force * elapsedTime; + } + } + + TorpedoParticle* torpedos = static_cast(m_torpedoGroup->GetBuffer()); + std::size_t torpedoCount = m_torpedoGroup->GetParticleCount(); + for (std::size_t j = 0; j < torpedoCount; ++j) + { + Nz::Spheref tordedoSphere(torpedos[j].position, 5.f); + + if (tordedoSphere.Contains(position)) + { + Nz::Spheref tordedoCenter(torpedos[j].position, 2.f); + if (tordedoCenter.Contains(position)) + { + group.KillParticle(i); + break; + } + + Nz::Vector3f dir = (torpedos[j].position - position); + float length; + dir.Normalize(&length); + + remainingLife -= 100.f * elapsedTime / length; + size -= 100.f * sizeGrowth * elapsedTime / length; + velocity += 500.f * dir * elapsedTime / length; + velocity += torpedos[j].velocity * elapsedTime; + + break; //< There's no way a particle would be in multiple torpedo at once + } + } + } + } + }); + + m_fireGroup->AddController(movementController); + m_fireGroup->AddController(Nz::ParticleFunctionController::New([] (Nz::ParticleGroup& group, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) + { + auto colorPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Color); + auto lifePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Life); + + float velFactor = std::pow(0.9f, elapsedTime / 0.1f); + for (unsigned int i = startId; i <= endId; ++i) + colorPtr[i].a = static_cast(Nz::Clamp(lifePtr[i] * 255.f, 0.f, 255.f)); + })); + + m_smokeGroup->AddController(movementController); + m_smokeGroup->AddController(Nz::ParticleFunctionController::New([] (Nz::ParticleGroup& group, Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) + { + auto colorPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Color); + auto lifePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Life); + + for (unsigned int i = startId; i <= endId; ++i) + { + float alpha = std::min((maxSmokeLife - lifePtr[i]) * 255.f / 5.f, 255.f); + alpha -= std::max((maxSmokeLife - lifePtr[i]) / maxSmokeLife * 255.f, 0.f); + + colorPtr[i].a = static_cast(Nz::Clamp(alpha, 0.f, 255.f)); + } + })); + + Nz::MaterialRef fireMat = Nz::Material::New("Translucent3D"); + fireMat->EnableFaceCulling(true); + fireMat->SetDiffuseMap("resources/fire_particle.png"); + // Additive blending for fire + fireMat->SetDstBlend(Nz::BlendFunc_One); + fireMat->SetSrcBlend(Nz::BlendFunc_SrcAlpha); + + Nz::MaterialRef smokeMat = Nz::Material::New("Translucent3D"); + smokeMat->EnableFaceCulling(true); + smokeMat->SetDiffuseColor(Nz::Color(128, 128, 128)); + smokeMat->SetDiffuseMap("resources/smoke.png"); + + m_fireGroup->SetRenderer(Nz::ParticleFunctionRenderer::New([fireMat] (const Nz::ParticleGroup& group, const Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, Nz::AbstractRenderQueue* renderQueue) + { + auto colorPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Color); + auto posPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto rotPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Rotation); + auto sizePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Size); + + renderQueue->AddBillboards(0, fireMat, endId - startId + 1, posPtr, sizePtr, rotPtr, colorPtr); + })); + + m_smokeGroup->SetRenderer(Nz::ParticleFunctionRenderer::New([smokeMat] (const Nz::ParticleGroup& group, const Nz::ParticleMapper& mapper, unsigned int startId, unsigned int endId, Nz::AbstractRenderQueue* renderQueue) + { + auto colorPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Color); + auto posPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); + auto rotPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Rotation); + auto sizePtr = mapper.GetComponentPtr(Nz::ParticleComponent_Size); + + renderQueue->AddBillboards(0, smokeMat, endId - startId + 1, posPtr, sizePtr, rotPtr, colorPtr); + })); + + ////////////////////////////////////////////////////////////////////////// + + m_ambientMusic.Play(); + m_turretFireSound.LoadFromFile("resources/turretFire.wav"); + m_turretReloadSound.LoadFromFile("resources/turretReload.wav"); + + //m_onMouseMoved.Connect(m_shared.target->GetEventHandler().OnMouseMoved, this, &SpacebattleExample::OnMouseMoved); + //m_shared.target->SetCursor(Nz::WindowCursor_None); + + ////////////////////////////////////////////////////////////////////////// + + Nz::TextSpriteRef introText = Nz::TextSprite::New(); + introText->Update(Nz::SimpleTextDrawer::Draw("--Tourelle de défense du secteur A407M2--\nLes contrôles ont été adaptés à vos contrôleurs:\nZQSD pour orienter la tourelle, espace pour tirer.\n", 72)); + introText->SetScale(0.5f); + + m_introText = m_shared.world3D->CreateEntity(); + Ndk::NodeComponent& introNode = m_introText->AddComponent(); + Ndk::GraphicsComponent& introGfx = m_introText->AddComponent(); + introGfx.Attach(introText, 1); + RegisterEntity(m_introText); + + Ndk::NodeComponent& cannonNode = m_turret.cannonEntity->GetComponent(); + + Nz::Boxf introAABB = introGfx.GetBoundingVolume().aabb; + introNode.SetPosition(cannonNode.GetForward() * 500.f + introNode.GetLeft() * introAABB.width / 2.f + introNode.GetUp() * introAABB.height / 2.f); +} + +void SpacebattleExample::Leave(Ndk::StateMachine& fsm) +{ + m_ambientMusic.Stop(); + m_shared.world3D->RemoveSystem(); + m_shared.world3D->RemoveSystem(); + m_turretFireSound.Stop(); + m_turretReloadSound.Stop(); + + ParticleDemo::Leave(fsm); +} + +bool SpacebattleExample::Update(Ndk::StateMachine& fsm, float elapsedTime) +{ + if (!ParticleDemo::Update(fsm, elapsedTime)) + return false; + + const float speed = 100.f; + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Z)) + m_turretCannonBaseRotation = std::max(m_turretCannonBaseRotation - speed * elapsedTime, -65.f); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::S)) + m_turretCannonBaseRotation = std::min(m_turretCannonBaseRotation + speed * elapsedTime, 40.f); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Q)) + m_turretBaseRotation += speed * elapsedTime; + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::D)) + m_turretBaseRotation -= speed * elapsedTime; + + m_turret.cannonBaseEntity->GetComponent().SetRotation(Nz::EulerAnglesf(m_turretCannonBaseRotation, 0.f, 0.f)); + m_turret.rotatingBaseEntity->GetComponent().SetRotation(Nz::EulerAnglesf(0.f, m_turretBaseRotation, 0.f)); + + bool discharged = m_turretShootTimer < 1.f; + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Space) && !discharged) + { + m_turretFireSound.Play(); + + m_turretShootTimer = -1.f; + + Ndk::NodeComponent& cannonNode = m_turret.cannonEntity->GetComponent(); + + TorpedoParticle* particle = static_cast(m_torpedoGroup->CreateParticle()); + particle->color = Nz::Color::White; + particle->position = cannonNode.ToGlobalPosition(Nz::Vector3f::Forward() * 10.f); + particle->rotation = 0.f; + particle->life = 15.f; + particle->size.Set(13.34f, 7.41f); + particle->size *= 2.f; + particle->velocity = cannonNode.GetForward() * 100.f; + } + + m_turretShootTimer += elapsedTime * 2.f; + if (discharged && m_turretShootTimer >= 1.f) + m_turretReloadSound.Play(); + + m_turret.cannonEntity->GetComponent().SetPosition(Nz::Vector3f::Backward() * std::sin(std::min(m_turretShootTimer, 0.f) * float(M_PI)) * 3.f); + + m_spaceshipSpawnCounter += elapsedTime; + if (m_spaceshipSpawnCounter >= 10.f) + { + m_spaceshipSpawnCounter -= 10.f; + + auto& spacestationNode = m_spacestationEntity->GetComponent(); + + Ndk::EntityHandle spaceship = m_spaceshipTemplate->Clone(); + RegisterEntity(spaceship); + auto& nodeComponent = spaceship->GetComponent(); + auto& spaceshipComponent = spaceship->GetComponent(); + + spaceshipComponent.targetPos = m_shared.viewer3D->GetComponent().GetPosition(); + nodeComponent.SetPosition(spacestationNode.GetPosition()); + nodeComponent.SetRotation(Nz::Quaternionf::RotationBetween(Nz::Vector3f::Forward(), spacestationNode.GetRight())); + nodeComponent.Move(Nz::Vector3f::Forward() * 15.f + Nz::Vector3f::Down() * 5.f, Nz::CoordSys_Local); + } + + m_introTimer -= elapsedTime; + if (m_introTimer <= 0.f && m_introText) + m_introText->Kill(); + + return true; +} + +void SpacebattleExample::CreateSpaceShip() +{ + m_spacestationEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_spacestationEntity); + + Ndk::NodeComponent& spacestationNode = m_spacestationEntity->AddComponent(); + spacestationNode.SetPosition(Nz::Vector3f::Forward() * 500.f + Nz::Vector3f::Up() * 200.f + Nz::Vector3f::Right() * 250.f); + spacestationNode.SetRotation(Nz::EulerAnglesf(0.f, 15.f, 0.f)); + spacestationNode.SetScale(0.1f); + + Ndk::GraphicsComponent& spacestationGfx = m_spacestationEntity->AddComponent(); + spacestationGfx.Attach(&m_spacestationModel); +} + +void SpacebattleExample::CreateTurret() +{ + // Fixed base + m_turret.baseEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_turret.baseEntity); + + Ndk::NodeComponent& baseNode = m_turret.baseEntity->AddComponent(); + //baseNode.SetParent(m_spacestationEntity); + baseNode.SetRotation(Nz::EulerAnglesf(0.f, 180.f, 0.f)); + + Ndk::GraphicsComponent& baseGfx = m_turret.baseEntity->AddComponent(); + baseGfx.Attach(&m_turret.baseModel); + + // Rotating base + m_turret.rotatingBaseEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_turret.rotatingBaseEntity); + + Ndk::NodeComponent& rotatingBaseNode = m_turret.rotatingBaseEntity->AddComponent(); + rotatingBaseNode.SetParent(m_turret.baseEntity); + + Ndk::GraphicsComponent& rotatingBaseGfx = m_turret.rotatingBaseEntity->AddComponent(); + rotatingBaseGfx.Attach(&m_turret.rotatingBaseModel); + + // Cannon base + m_turret.cannonBaseEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_turret.cannonBaseEntity); + + Ndk::NodeComponent& cannonBaseNode = m_turret.cannonBaseEntity->AddComponent(); + cannonBaseNode.SetPosition({0.f, 3.39623547f, 0.f}); + cannonBaseNode.SetParent(m_turret.rotatingBaseEntity); + + Ndk::GraphicsComponent& cannonBaseGfx = m_turret.cannonBaseEntity->AddComponent(); + cannonBaseGfx.Attach(&m_turret.cannonBaseModel); + + // Cannon anchor + m_turret.cannonAnchorEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_turret.cannonAnchorEntity); + + Ndk::NodeComponent& cannonAnchorNode = m_turret.cannonAnchorEntity->AddComponent(); + cannonAnchorNode.SetPosition({0.f, 2.96482944f, 3.20705462f}); + cannonAnchorNode.SetParent(m_turret.cannonBaseEntity); + + // Cannon + m_turret.cannonEntity = m_shared.world3D->CreateEntity(); + RegisterEntity(m_turret.cannonEntity); + + Ndk::NodeComponent& cannonNode = m_turret.cannonEntity->AddComponent(); + cannonNode.SetParent(m_turret.cannonAnchorEntity); + cannonNode.SetRotation(Nz::EulerAnglesf(0.f, 180.f, 0.f)); + + Ndk::GraphicsComponent& cannonGfx = m_turret.cannonEntity->AddComponent(); + cannonGfx.Attach(&m_turret.cannonModel); +} + +void SpacebattleExample::OnMouseMoved(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseMoveEvent& event) +{ + const float speed = 0.1f; + + m_turretCannonBaseRotation = Nz::Clamp(m_turretCannonBaseRotation + speed * event.deltaY, -65.f, 40.f); + m_turretBaseRotation -= event.deltaX * speed; + + Nz::Mouse::SetPosition(m_shared.target->GetWidth() / 2, m_shared.target->GetHeight() / 2, *m_shared.target); +} diff --git a/examples/Particles/SpacebattleDemo.hpp b/examples/Particles/SpacebattleDemo.hpp new file mode 100644 index 000000000..44852a61f --- /dev/null +++ b/examples/Particles/SpacebattleDemo.hpp @@ -0,0 +1,71 @@ +#pragma once + +#ifndef NAZARA_EXAMPLES_PARTICLES_SPACEBATTLE_HPP +#define NAZARA_EXAMPLES_PARTICLES_SPACEBATTLE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common.hpp" + +class SpacebattleExample : public ParticleDemo +{ + public: + SpacebattleExample(ExampleShared& sharedData); + ~SpacebattleExample() = default; + + void Enter(Ndk::StateMachine& fsm) override; + void Leave(Ndk::StateMachine& fsm) override; + bool Update(Ndk::StateMachine& fsm, float elapsedTime) override; + + private: + void CreateSpaceShip(); + void CreateTurret(); + void OnMouseMoved(const Nz::EventHandler* eventHandler, const Nz::WindowEvent::MouseMoveEvent& event); + + struct Turret + { + Nz::Model baseModel; + Nz::Model cannonModel; + Nz::Model cannonBaseModel; + Nz::Model rotatingBaseModel; + Ndk::EntityHandle baseEntity; + Ndk::EntityHandle cannonAnchorEntity; + Ndk::EntityHandle cannonEntity; + Ndk::EntityHandle cannonBaseEntity; + Ndk::EntityHandle rotatingBaseEntity; + }; + + Turret m_turret; + float m_introTimer; + float m_spaceshipSpawnCounter; + float m_turretBaseRotation; + float m_turretCannonBaseRotation; + float m_turretShootTimer; + Nz::Model m_spaceshipModel; + Nz::Model m_spacestationModel; + Nz::Music m_ambientMusic; + Nz::ParticleDeclarationRef m_torpedoDeclaration; + Nz::ParticleRendererRef m_laserBeamRenderer; + Nz::Sound m_turretFireSound; + Nz::Sound m_turretReloadSound; + Nz::SkyboxBackground m_skybox; + Ndk::EntityHandle m_introText; + Ndk::EntityHandle m_spaceshipTemplate; + Ndk::EntityHandle m_spacestationEntity; + Ndk::ParticleGroupComponentHandle m_fireGroup; + Ndk::ParticleGroupComponentHandle m_smokeGroup; + Ndk::ParticleGroupComponentHandle m_torpedoGroup; + + NazaraSlot(Nz::EventHandler, OnMouseMoved, m_onMouseMoved); +}; + +#endif // NAZARA_EXAMPLES_PARTICLES_SPACEBATTLE_HPP diff --git a/examples/Particles/build.lua b/examples/Particles/build.lua new file mode 100644 index 000000000..2e1a37801 --- /dev/null +++ b/examples/Particles/build.lua @@ -0,0 +1,13 @@ +EXAMPLE.Name = "Particles" + +EXAMPLE.EnableConsole = true + +EXAMPLE.Files = { + "*.hpp", + "*.inl", + "*.cpp" +} + +EXAMPLE.Libraries = { + "NazaraSDK" +} diff --git a/examples/Particles/main.cpp b/examples/Particles/main.cpp new file mode 100644 index 000000000..67bf99754 --- /dev/null +++ b/examples/Particles/main.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "LogoDemo.hpp" +#include "SpacebattleDemo.hpp" +#include + +int main() +{ + Nz::ContextParameters::defaultCompatibilityProfile = true; + + Ndk::Application app; + + // Mix all sounds in mono (in order to give them 3D position) + Nz::SoundBufferParams soundParams; + soundParams.forceMono = true; + + Nz::SoundBufferManager::SetDefaultParameters(soundParams); + + // Pour commencer le mode vidéo, celui-ci va définir la taille de la zone de rendu et le nombre de bits par pixels + Nz::VideoMode mode = Nz::VideoMode::GetDesktopMode(); // Nous récupérons le mode vidéo du bureau + + // Nous allons prendre les trois quarts de la résolution du bureau pour notre fenêtre + mode.width = 3 * mode.width / 4; + mode.height = 3 * mode.height / 4; + + Nz::ContextParameters targetParams; + targetParams.antialiasingLevel = 0; + + Nz::RenderWindow& window = app.AddWindow(mode, "Nazara demo - Particles", Nz::WindowStyle_Closable, targetParams); + //Nz::RenderWindow& window = app.AddWindow(Nz::VideoMode(1920, 1080), "Nazara demo - Particles", Nz::WindowStyle_Fullscreen, targetParams); + + Ndk::World& world3D = app.AddWorld(); + Ndk::World& world2D = app.AddWorld(); + + std::random_device randomDevice; + + ExampleShared shared; + shared.randomGen.seed(randomDevice()); + shared.target = &window; + shared.world2D = &world2D; + shared.world3D = &world3D; + + shared.demoName = Nz::TextSprite::New(); + shared.demoName->Update(Nz::SimpleTextDrawer::Draw("XX - DemoName", 48)); + + shared.fpsCount = Nz::TextSprite::New(); + shared.fpsCount->Update(Nz::SimpleTextDrawer::Draw("XXXXX FPS", 24)); + + shared.particleCount = Nz::TextSprite::New(); + shared.particleCount->Update(Nz::SimpleTextDrawer::Draw("XXXXX particles", 36)); + + world2D.GetSystem().SetGlobalUp(Nz::Vector3f::Down()); + world3D.GetSystem().ChangeRenderTechnique(); + + + Ndk::EntityHandle viewEntity = world2D.CreateEntity(); + viewEntity->AddComponent(); + + Ndk::CameraComponent& viewer = viewEntity->AddComponent(); + viewer.SetTarget(&window); + viewer.SetProjectionType(Nz::ProjectionType_Orthogonal); + + shared.viewer2D = viewEntity; + + Ndk::EntityHandle cameraEntity = world3D.CreateEntity(); + cameraEntity->AddComponent(); + cameraEntity->AddComponent(); + + Ndk::CameraComponent& camera = cameraEntity->AddComponent(); + camera.SetTarget(&window); + camera.SetZFar(10000.f); + + shared.viewer3D = cameraEntity; + + Ndk::EntityHandle demoNameEntity = world2D.CreateEntity(); + Ndk::NodeComponent& demoNameNode = demoNameEntity->AddComponent(); + Ndk::GraphicsComponent& demoNameGfx = demoNameEntity->AddComponent(); + demoNameGfx.Attach(shared.demoName, 1); + + Ndk::EntityHandle fpsCountEntity = world2D.CreateEntity(); + Ndk::NodeComponent& fpsNode = fpsCountEntity->AddComponent(); + Ndk::GraphicsComponent& fpsGfx = fpsCountEntity->AddComponent(); + fpsGfx.Attach(shared.fpsCount, 1); + + Ndk::EntityHandle particleCountEntity = world2D.CreateEntity(); + Ndk::NodeComponent& particleCountNode = particleCountEntity->AddComponent(); + Ndk::GraphicsComponent& particleCountGfx = particleCountEntity->AddComponent(); + particleCountGfx.Attach(shared.particleCount, 1); + + + Nz::Boxf fpsCountBox = fpsGfx.GetBoundingVolume().aabb; + Nz::Boxf particleCountBox = particleCountGfx.GetBoundingVolume().aabb; + + demoNameNode.SetPosition(5.f, 5.f); + particleCountNode.SetPosition(5.f, window.GetHeight() - particleCountBox.height - 5.f); + fpsNode.SetPosition(5.f, window.GetHeight() - fpsCountBox.height - particleCountBox.height - 5.f); + + + //shared.demos.push_back(std::make_shared(shared)); + shared.demos.push_back(std::make_shared(shared)); + + std::size_t demoIndex = 0; + Ndk::StateMachine stateMachine(shared.demos[demoIndex]); + + window.EnableEventPolling(true); + + while (app.Run()) + { + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType_KeyPressed: + { + switch (event.key.code) + { + case Nz::Keyboard::Backspace: + stateMachine.ChangeState(stateMachine.GetCurrentState()); + break; + + case Nz::Keyboard::Escape: + app.Quit(); + break; + + case Nz::Keyboard::Left: + { + if (shared.demos.size() <= 1) + break; + + if (demoIndex == 0) + demoIndex = shared.demos.size(); + + demoIndex--; + stateMachine.ChangeState(shared.demos[demoIndex]); + break; + } + + case Nz::Keyboard::Right: + { + if (shared.demos.size() <= 1) + break; + + demoIndex++; + if (demoIndex == shared.demos.size()) + demoIndex = 0; + + stateMachine.ChangeState(shared.demos[demoIndex]); + break; + } + + case Nz::Keyboard::Pause: + { + auto& velocitySystem = shared.world3D->GetSystem(); + velocitySystem.Enable(!velocitySystem.IsEnabled()); + break; + } + + case Nz::Keyboard::F5: + { + Nz::Image screenshot; + screenshot.Create(Nz::ImageType_2D, Nz::PixelFormatType_RGBA8, 1920, 1080); + window.CopyToImage(&screenshot); + + static unsigned int counter = 1; + screenshot.SaveToFile("screenshot_" + Nz::String::Number(counter++) + ".png"); + break; + } + + default: + break; + } + break; + } + + case Nz::WindowEventType_Quit: + window.Close(); + break; + + default: + break; + } + } + + stateMachine.Update(app.GetUpdateTime()); + + window.Display(); + } + + return EXIT_SUCCESS; +}