#include "LogoDemo.hpp" #include #include #include #include #include #include namespace { const float duration = 10.f; const float maxSpeed = 100.f; const float maxMouseForce = 1000.f; const float mouseForce = 500.f; const float pauseTime = 3.f; const float startTime = 2.f; const float speed = 3.f; } struct ParticleData { Nz::Color color; Nz::Vector2f destination; Nz::Vector3f position; Nz::Vector2f velocity; }; 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 destPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Userdata0); auto posPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Position); auto velPtr = mapper.GetComponentPtr(Nz::ParticleComponent_Velocity); std::uniform_real_distribution dis(-1.f, 1.f); for (unsigned int i = startId; i <= endId; ++i) { Nz::Vector2f newVel = destPtr[i] - Nz::Vector2f(posPtr[i]); float length; newVel.Normalize(&length); float distance = SquaredDistancePointSegment(oldMousePos, actualMousePos, Nz::Vector2f(posPtr[i])); if (distance < 250.f) { Nz::Vector2f mouseLine = actualMousePos - oldMousePos; float mouseLength; mouseLine.Normalize(&mouseLength); if (mouseLength > 5.f) { velPtr[i] += mouseLine * std::min(mouseLength * mouseForce, maxMouseForce) * elapsedTime; velPtr[i] += Nz::Vector2f(dis(randomGen), dis(randomGen)) * std::min(mouseLength, maxMouseForce * 0.1f); } } if (length > 1.f || velPtr[i].GetSquaredLength() > Nz::IntegralPow(30.f, 2)) { newVel *= maxSpeed; velPtr[i] = Nz::Lerp(velPtr[i], newVel, 0.4f * elapsedTime); posPtr[i] += velPtr[i] * elapsedTime; } else { velPtr[i] = Nz::Vector2f::Zero(); posPtr[i] = destPtr[i]; } } } static float SquaredDistancePointSegment(const Nz::Vector2f& s0, const Nz::Vector2f& s1, const Nz::Vector2f& point) { // http://geomalgorithms.com/a02-_lines.html Nz::Vector2f v = s1 - s0; Nz::Vector2f w = point - s0; float c1 = Nz::Vector2f::DotProduct(w, v); if (c1 <= 0.f) return point.SquaredDistance(s0); float c2 = Nz::Vector2f::DotProduct(v, v); if (c2 <= c1) return point.SquaredDistance(s1); float b = c1 / c2; Nz::Vector2f projPoint = s0 + b * v; return projPoint.SquaredDistance(point); } std::mt19937 randomGen; bool enabled = false; float factor = 1000.f; Nz::Vector2f actualMousePos; Nz::Vector2f oldMousePos; }; 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) override { 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, Nz::Recti(-1, -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::PixelFormat_RGBA8; m_logo = Nz::Image::LoadFromFile("E:/Twitch/avatar_interested.png", params); if (!m_logo) 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 x = 0; x < width; ++x) { for (unsigned int y = 0; y < height; ++y) { 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)); m_declaration = Nz::ParticleDeclaration::New(); m_declaration->EnableComponent(Nz::ParticleComponent_Color, Nz::ComponentType_Color, NazaraOffsetOf(ParticleData, color)); m_declaration->EnableComponent(Nz::ParticleComponent_Position, Nz::ComponentType_Float3, NazaraOffsetOf(ParticleData, position)); m_declaration->EnableComponent(Nz::ParticleComponent_Userdata0, Nz::ComponentType_Float2, NazaraOffsetOf(ParticleData, destination)); m_declaration->EnableComponent(Nz::ParticleComponent_Velocity, Nz::ComponentType_Float2, NazaraOffsetOf(ParticleData, velocity)); } void LogoExample::Enter(Ndk::StateMachine& fsm) { ParticleDemo::Enter(fsm); m_shared.world3D->GetSystem().SetDefaultBackground(nullptr); Nz::TextureRef backgroundTexture = Nz::Texture::LoadFromFile("resources/stars-background.jpg"); if (backgroundTexture) 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(), m_declaration); RegisterParticleGroup(particleGroupEntity); particleGroup.AddController(m_controller); particleGroup.SetRenderer(m_renderer); m_particles = particleGroup.CreateParticles(m_pixels.size()); ResetParticles(-duration * (speed / 2.f)); m_accumulator = pauseTime + duration; m_totalAccumulator = 0.f; SpriteController* controller = static_cast(m_controller.Get()); controller->actualMousePos = controller->oldMousePos = Nz::Vector2f(Nz::Mouse::GetPosition(*m_shared.target)); } 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()); controller->enabled = (m_accumulator > pauseTime); if (m_mouseClock.GetMilliseconds() > 1000/30) { m_mouseClock.Restart(); controller->oldMousePos = controller->actualMousePos; controller->actualMousePos = Nz::Vector2f(Nz::Mouse::GetPosition(*m_shared.target)); } if (Nz::Mouse::IsButtonPressed(Nz::Mouse::Left)) { if (!m_hasClicked) { m_hasClicked = true; std::uniform_real_distribution dis(50.f, 60.f); ParticleData* sprite = static_cast(m_particles); for (std::size_t i = 0; i < m_pixels.size(); ++i) { Nz::Vector2f particleToMouse = Nz::Vector2f(sprite[i].position) - controller->actualMousePos; float sqDist = particleToMouse.GetSquaredLength(); if (sqDist < 10000.f) { float dist = std::sqrt(sqDist); particleToMouse /= std::max(dist, 1.f); sprite[i].velocity += particleToMouse * dis(m_shared.randomGen); } } } } else m_hasClicked = false; return true; } void LogoExample::ResetParticles(float elapsed) { Nz::Vector2ui size = m_shared.target->GetSize(); Nz::Vector2f center = {size.x / 2.f, size.y / 2.f}; Nz::Vector2f offset = center - Nz::Vector2f(Nz::Vector2ui(m_logo->GetSize()) / 2); std::uniform_real_distribution disX(0.f, float(size.x)); std::uniform_real_distribution disY(-float(size.y) * 0.5f, float(size.y) * 1.5f); ParticleData* sprite = static_cast(m_particles); for (PixelData& data : m_pixels) { sprite->color = data.color; sprite->destination = offset + Nz::Vector2f(data.pos); sprite->position.Set(disX(m_shared.randomGen) - float(size.x), disY(m_shared.randomGen), 0.f); sprite->velocity = Nz::Vector2f::Zero(); sprite++; } }