void FreeRetiredParticles(SmokeParticleComponent particleComp) { while (particleComp.firstRetiredParticle != particleComp.firstActiveParticle) { // Has this particle been unused long enough that // the GPU is sure to be finished with it? // We multiply the retired particle index by four, because each // particle consists of a quad that is made up of four vertices. int age = particleComp.drawCounter - (int)particleComp.particles[particleComp.firstRetiredParticle * 4].Time; // The GPU is never supposed to get more than 2 frames behind the CPU. // We add 1 to that, just to be safe in case of buggy drivers that // might bend the rules and let the GPU get further behind. if (age < 3) { break; } // Move the particle from the retired to the free queue. particleComp.firstRetiredParticle++; if (particleComp.firstRetiredParticle >= particleComp.MaxParticles) { particleComp.firstRetiredParticle = 0; } } }
void AddNewParticlesToVertexBuffer(SmokeParticleComponent particleComp) { int stride = ParticleVertex.SizeInBytes; if (particleComp.firstNewParticle < particleComp.firstFreeParticle) { // If the new particles are all in one consecutive range, // we can upload them all in a single call. particleComp.vertexBuffer.SetData(particleComp.firstNewParticle * stride * 4, particleComp.particles, particleComp.firstNewParticle * 4, (particleComp.firstFreeParticle - particleComp.firstNewParticle) * 4, stride, SetDataOptions.NoOverwrite); } else { // If the new particle range wraps past the end of the queue // back to the start, we must split them over two upload calls. particleComp.vertexBuffer.SetData(particleComp.firstNewParticle * stride * 4, particleComp.particles, particleComp.firstNewParticle * 4, (particleComp.MaxParticles - particleComp.firstNewParticle) * 4, stride, SetDataOptions.NoOverwrite); if (particleComp.firstFreeParticle > 0) { particleComp.vertexBuffer.SetData(0, particleComp.particles, 0, particleComp.firstFreeParticle * 4, stride, SetDataOptions.NoOverwrite); } } // Move the particles we just uploaded from the new to the active queue. particleComp.firstNewParticle = particleComp.firstFreeParticle; }
void RetireActiveParticles(SmokeParticleComponent particleComp) { float particleDuration = (float)particleComp.Duration.TotalSeconds; while (particleComp.firstActiveParticle != particleComp.firstNewParticle) { // Is this particle old enough to retire? // We multiply the active particle index by four, because each // particle consists of a quad that is made up of four vertices. float particleAge = particleComp.currentTime - particleComp.particles[particleComp.firstActiveParticle * 4].Time; if (particleAge < particleDuration) { break; } // Remember the time at which we retired this particle. particleComp.particles[particleComp.firstActiveParticle * 4].Time = particleComp.drawCounter; // Move the particle from the active to the retired queue. particleComp.firstActiveParticle++; if (particleComp.firstActiveParticle >= particleComp.MaxParticles) { particleComp.firstActiveParticle = 0; } } }
public void Update(GameTime gameTime) { if (gameTime == null) { throw new ArgumentNullException("gameTime"); } List <Entity> particlesEnt = ComponentManager.Instance.GetAllEntitiesWithComponentType <SmokeParticleComponent>(); if (particlesEnt == null) { return; } foreach (Entity e in particlesEnt) { SmokeParticleComponent pc = ComponentManager.Instance.GetEntityComponent <SmokeParticleComponent>(e); var transformEntities = ComponentManager.Instance.GetAllEntitiesWithComponentType <TransformComponent>(); var transformEntity = ComponentManager.Instance.GetEntityWithTag("Kart", transformEntities); TransformComponent tc = ComponentManager.Instance.GetEntityComponent <TransformComponent>(transformEntity); pc.currentTime += (float)gameTime.ElapsedGameTime.TotalSeconds; RetireActiveParticles(pc); FreeRetiredParticles(pc); ParticleRenderSystem.AddParticle(pc, Vector3.Zero, Vector3.Zero); //get the position and rotation of the kart Matrix PosRotMatrix = Matrix.CreateTranslation(-tc.Position) * Matrix.CreateFromQuaternion(tc.Rotation) * Matrix.CreateTranslation(tc.Position); //Set the particles position to the models position, applying the position and offset with the Positin and rotation of the Vector3 newPos = Vector3.Transform(tc.Position + pc.positionOffset, PosRotMatrix); // If we let our timer go on increasing for ever, it would eventually // run out of floating point precision, at which point the particles // would render incorrectly. An easy way to prevent this is to notice // that the time value doesn't matter when no particles are being drawn, // so we can reset it back to zero any time the active queue is empty. if (pc.firstActiveParticle == pc.firstFreeParticle) { pc.currentTime = 0; } if (pc.firstRetiredParticle == pc.firstActiveParticle) { pc.drawCounter = 0; } pc.effect.Parameters["position"].SetValue(newPos); } }
private void InitParticles(ECSEngine engine) { SystemManager.Instance.RegisterSystem("Game", new ParticleRenderSystem(engine.GetGraphicsDevice())); SystemManager.Instance.RegisterSystem("Game", new ParticleUpdateSystem()); Entity SmokehParticle = EntityFactory.Instance.NewEntityWithTag("smokeh"); SmokeParticleComponent pComp = new SmokeParticleComponent(); ComponentManager.Instance.AddComponentToEntity(SmokehParticle, pComp); ParticleRenderSystem.LoadParticleEffect(engine.GetGraphicsDevice(), engine.LoadContent <Effect>("Effects/ParticleEffect"), engine.LoadContent <Texture2D>("smoke"), ref pComp); ParticleRenderSystem.setParticleOffsetPosition(ref pComp, new Vector3(0, 0, 10f)); SceneManager.Instance.AddEntityToSceneOnLayer("Game", 2, SmokehParticle); }
public static void AddParticle(SmokeParticleComponent particleComp, Vector3 position, Vector3 velocity) { // Figure out where in the circular queue to allocate the new particle. int nextFreeParticle = particleComp.firstFreeParticle + 1; if (nextFreeParticle >= particleComp.MaxParticles) { nextFreeParticle = 0; } // If there are no free particles, we just have to give up. if (nextFreeParticle == particleComp.firstRetiredParticle) { return; } // Adjust the input velocity based on how much // this particle system wants to be affected by it. velocity *= particleComp.EmitterVelocitySensitivity; // Add in some random amount of horizontal velocity. float horizontalVelocity = MathHelper.Lerp(particleComp.MinHorizontalVelocity, particleComp.MaxHorizontalVelocity, (float)random.NextDouble()); double horizontalAngle = random.NextDouble() * MathHelper.TwoPi; velocity.X += horizontalVelocity * (float)Math.Cos(horizontalAngle); velocity.Z += horizontalVelocity * (float)Math.Sin(horizontalAngle); // Add in some random amount of vertical velocity. velocity.Y += MathHelper.Lerp(particleComp.MinVerticalVelocity, particleComp.MaxVerticalVelocity, (float)random.NextDouble()); // Choose four random control values. These will be used by the vertex // shader to give each particle a different size, rotation, and color. Color randomValues = new Color((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)); // Fill in the particle vertex structure. for (int i = 0; i < 4; i++) { particleComp.particles[particleComp.firstFreeParticle * 4 + i].Position = position; particleComp.particles[particleComp.firstFreeParticle * 4 + i].Velocity = velocity; particleComp.particles[particleComp.firstFreeParticle * 4 + i].Random = randomValues; particleComp.particles[particleComp.firstFreeParticle * 4 + i].Time = particleComp.currentTime; } particleComp.firstFreeParticle = nextFreeParticle; }
public ParticleEmitter(SmokeParticleComponent particleComp, float particlesPerSecond, Vector3 initialPosition) { this.particleComp = particleComp; timeBetweenParticles = 1.0f / particlesPerSecond; previousPosition = initialPosition; }
public void Render(GraphicsDevice graphicsDevice, GameTime gameTime) { List <Entity> particleEnts = ComponentManager.Instance.GetAllEntitiesWithComponentType <SmokeParticleComponent>(); Entity cameraEnt = ComponentManager.Instance.GetFirstEntityOfType <CameraComponent>(); CameraComponent c = ComponentManager.Instance.GetEntityComponent <CameraComponent>(cameraEnt); foreach (Entity e in particleEnts) { SmokeParticleComponent pc = ComponentManager.Instance.GetEntityComponent <SmokeParticleComponent>(e); //// Restore the vertex buffer if context is lost //if (pc.vertexBuffer.IsContentLost) //{ // pc.vertexBuffer.SetData(pc.particles); //} // If there are any particles waiting in the newly added queue, // we'd better upload them to the GPU ready for drawing. if (pc.firstNewParticle != pc.firstFreeParticle) { AddNewParticlesToVertexBuffer(pc); } // If there are any active particles, draw them now! if (pc.firstActiveParticle != pc.firstFreeParticle) { graphicsDevice.BlendState = pc.BlendState; graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; // Set an effect parameter describing the current time. All the vertex // shader particle animation is keyed off this value. effectTimeParameter.SetValue(pc.currentTime); // Set the particle vertex and index buffer. graphicsDevice.SetVertexBuffer(pc.vertexBuffer); graphicsDevice.Indices = pc.indexBuffer; effectViewParameter.SetValue(c.viewMatrix); effectProjectionParameter.SetValue(c.projectionMatrix); effectViewportScaleParameter.SetValue(new Vector2(0.5f / device.Viewport.AspectRatio, -0.5f)); // Activate the particle effect. foreach (EffectPass pass in pc.effect.CurrentTechnique.Passes) { pass.Apply(); if (pc.firstActiveParticle < pc.firstFreeParticle) { // If the active particles are all in one consecutive range, // we can draw them all in a single call. graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, pc.firstActiveParticle * 4, (pc.firstFreeParticle - pc.firstActiveParticle) * 4, pc.firstActiveParticle * 6, (pc.firstFreeParticle - pc.firstActiveParticle) * 2); } else { // If the active particle range wraps past the end of the queue // back to the start, we must split them over two draw calls. graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, pc.firstActiveParticle * 4, (pc.MaxParticles - pc.firstActiveParticle) * 4, pc.firstActiveParticle * 6, (pc.MaxParticles - pc.firstActiveParticle) * 2); if (pc.firstFreeParticle > 0) { graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, pc.firstFreeParticle * 4, 0, pc.firstFreeParticle * 2); } } } // Reset some of the renderstates that we changed, // so as not to mess up any other subsequent drawing. graphicsDevice.DepthStencilState = DepthStencilState.Default; } pc.drawCounter++; } }
public static void LoadParticleEffect(GraphicsDevice device, Effect effect, Texture2D particleTexture, ref SmokeParticleComponent particleComp) { // If we have several particle systems, the content manager will return // a single shared effect instance to them all. But we want to preconfigure // the effect with parameters that are specific to this particular // particle system. By cloning the effect, we prevent one particle system // from stomping over the parameter settings of another. particleComp.effect = effect.Clone(); EffectParameterCollection parameters = particleComp.effect.Parameters; effectViewParameter = parameters["View"]; effectProjectionParameter = parameters["Projection"]; effectViewportScaleParameter = parameters["ViewportScale"]; // Look up shortcuts for parameters that change every frame. effectTimeParameter = parameters["CurrentTime"]; // Set the values of parameters that do not change. parameters["Duration"].SetValue((float)particleComp.Duration.TotalSeconds); parameters["DurationRandomness"].SetValue(particleComp.LifeDurationRandomness); parameters["Gravity"].SetValue(particleComp.Gravity); parameters["EndVelocity"].SetValue(particleComp.EndVelocity); parameters["MinColor"].SetValue(particleComp.MinColor.ToVector4()); parameters["MaxColor"].SetValue(particleComp.MaxColor.ToVector4()); parameters["RotateSpeed"].SetValue( new Vector2(particleComp.MinRotateSpeed, particleComp.MaxRotateSpeed)); parameters["StartSize"].SetValue( new Vector2(particleComp.MinStartSize, particleComp.MaxStartSize)); parameters["EndSize"].SetValue( new Vector2(particleComp.MinEndSize, particleComp.MaxEndSize)); parameters["Texture"].SetValue(particleTexture); // Create a dynamic vertex buffer. particleComp.vertexBuffer = new DynamicVertexBuffer(device, ParticleVertex.VertexDeclaration, particleComp.MaxParticles * 4, BufferUsage.WriteOnly); // Create and populate the index buffer. ushort[] indices = new ushort[particleComp.MaxParticles * 6]; for (int i = 0; i < particleComp.MaxParticles; i++) { indices[i * 6 + 0] = (ushort)(i * 4 + 0); indices[i * 6 + 1] = (ushort)(i * 4 + 1); indices[i * 6 + 2] = (ushort)(i * 4 + 2); indices[i * 6 + 3] = (ushort)(i * 4 + 0); indices[i * 6 + 4] = (ushort)(i * 4 + 2); indices[i * 6 + 5] = (ushort)(i * 4 + 3); } particleComp.indexBuffer = new IndexBuffer(device, typeof(ushort), indices.Length, BufferUsage.WriteOnly); particleComp.indexBuffer.SetData(indices); }
public static void setParticleOffsetPosition(ref SmokeParticleComponent particleComp, Vector3 positionOffset) { particleComp.positionOffset = positionOffset; }