/// <inheritdoc/> public void DrawRibbon(ref RibbonArgs p0, ref RibbonArgs p1, PackedTexture texture) { int index, dummy; _renderBatch.Submit(PrimitiveType.TriangleList, 4, 6, out index, out dummy); OnDrawRibbon(ref p0, ref p1, texture, _renderBatch.Vertices, index); }
protected override void OnDrawRibbon(ref RibbonArgs p0, ref RibbonArgs p1, PackedTexture texture, BillboardVertex[] vertices, int index) { // p0 and p1 specify a segment of a particle ribbon. // --+--------------+-- // p0 p1 // --+--------------+-- // Bottom left vertex var v = new BillboardVertex(); v.Position = p0.Position; v.Axis = p0.Axis; v.Color3F = p0.Color; v.Alpha = p0.Alpha; v.TextureCoordinate = new Vector2F(p0.TextureCoordinateU, 1); v.Size = new Vector2F(p0.Size); v.Softness = p0.Softness; v.ReferenceAlpha = p0.ReferenceAlpha; v.AnimationTime = p0.AnimationTime; v.BlendMode = p0.BlendMode; v.Texture = texture; vertices[index] = v; index++; // Top left vertex v.TextureCoordinate.Y = 0; vertices[index] = v; index++; // Top right vertex v.Position = p1.Position; v.Axis = p1.Axis; v.Color3F = p1.Color; v.Alpha = p1.Alpha; v.TextureCoordinate = new Vector2F(p1.TextureCoordinateU, 0); v.Size = new Vector2F(p1.Size); v.Softness = p1.Softness; v.ReferenceAlpha = p1.ReferenceAlpha; v.AnimationTime = p1.AnimationTime; v.BlendMode = p1.BlendMode; vertices[index] = v; index++; // Bottom right vertex v.TextureCoordinate.Y = 1; vertices[index] = v; }
protected override void OnDrawRibbon(ref RibbonArgs p0, ref RibbonArgs p1, PackedTexture texture, VertexPositionColorTexture[] vertices, int index) { // p0 and p1 specify a segment of a particle ribbon. // --+--------------+-- // p0 p1 // --+--------------+-- #region ----- Handle texture information and size ----- float animationTime = p0.AnimationTime; Vector2F texCoordTopLeft = texture.GetTextureCoordinates(new Vector2F(p0.TextureCoordinateU, 0), animationTime); Vector2F texCoordBottomRight = texture.GetTextureCoordinates(new Vector2F(p1.TextureCoordinateU, 1), animationTime); // Negative sizes (mirroring) is not supported because this conflicts with the // texture tiling on ribbons. float size0 = Math.Abs(p0.Size) / 2; float size1 = Math.Abs(p1.Size) / 2; // Offset from particle center to upper edge. Vector3F up0; up0.X = p0.Axis.X * size0; up0.Y = p0.Axis.Y * size0; up0.Z = p0.Axis.Z * size0; Vector3F up1; up1.X = p1.Axis.X * size1; up1.Y = p1.Axis.Y * size1; up1.Z = p1.Axis.Z * size1; #endregion #region ----- Get Color ----- // Premultiply alpha. Vector4 color4 = new Vector4 { X = p0.Color.X * p0.Alpha, Y = p0.Color.Y * p0.Alpha, Z = p0.Color.Z * p0.Alpha, // Apply blend mode (0 = additive, 1 = alpha blend). W = p0.Alpha * p0.BlendMode }; var color0 = new Color(color4); color4 = new Vector4 { X = p1.Color.X * p1.Alpha, Y = p1.Color.Y * p1.Alpha, Z = p1.Color.Z * p1.Alpha, W = p1.Alpha * p1.BlendMode }; var color1 = new Color(color4); #endregion #region ----- Initializes vertices in vertex array ----- // Bottom left vertex vertices[index].Position.X = p0.Position.X - up0.X; vertices[index].Position.Y = p0.Position.Y - up0.Y; vertices[index].Position.Z = p0.Position.Z - up0.Z; vertices[index].Color = color0; vertices[index].TextureCoordinate.X = texCoordTopLeft.X; vertices[index].TextureCoordinate.Y = texCoordBottomRight.Y; index++; // Top left vertex vertices[index].Position.X = p0.Position.X + up0.X; vertices[index].Position.Y = p0.Position.Y + up0.Y; vertices[index].Position.Z = p0.Position.Z + up0.Z; vertices[index].Color = color0; vertices[index].TextureCoordinate.X = texCoordTopLeft.X; vertices[index].TextureCoordinate.Y = texCoordTopLeft.Y; index++; // Top right vertex vertices[index].Position.X = p1.Position.X + up1.X; vertices[index].Position.Y = p1.Position.Y + up1.Y; vertices[index].Position.Z = p1.Position.Z + up1.Z; vertices[index].Color = color1; vertices[index].TextureCoordinate.X = texCoordBottomRight.X; vertices[index].TextureCoordinate.Y = texCoordTopLeft.Y; index++; // Bottom right vertex vertices[index].Position.X = p1.Position.X - up1.X; vertices[index].Position.Y = p1.Position.Y - up1.Y; vertices[index].Position.Z = p1.Position.Z - up1.Z; vertices[index].Color = color1; vertices[index].TextureCoordinate.X = texCoordBottomRight.X; vertices[index].TextureCoordinate.Y = texCoordBottomRight.Y; #endregion }
private void DrawParticleRibbonsAuto(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3F scale, ref Pose pose, ref Vector3F color, float alpha) { // At least two particles are required to create a ribbon. int numberOfParticles = particleSystemData.Particles.Count; if (numberOfParticles < 2) { return; } // The up axis is not defined and needs to be derived automatically: // - Compute tangents along the ribbon curve. // - Build cross-products of normal and tangent vectors. // Is normal uniform across all particles? Vector3F?uniformNormal; switch (particleSystemData.BillboardOrientation.Normal) { case BillboardNormal.ViewPlaneAligned: uniformNormal = _defaultNormal; break; case BillboardNormal.ViewpointOriented: uniformNormal = _cameraPose.Position - pose.Position; if (!uniformNormal.Value.TryNormalize()) { uniformNormal = _defaultNormal; } break; default: var normalParameter = particleSystemData.NormalParameter; if (normalParameter == null) { uniformNormal = _defaultNormal; } else if (normalParameter.IsUniform) { uniformNormal = normalParameter.DefaultValue; if (requiresTransformation) { uniformNormal = pose.ToWorldDirection(uniformNormal.Value); } } else { // Normal is set in particle data. uniformNormal = null; } break; } var texture = particleSystemData.Texture ?? _debugTexture; var particles = particleSystemData.Particles.Array; int index = 0; do { // ----- Skip dead particles. while (index < numberOfParticles && !particles[index].IsAlive) { index++; } // ----- Start of new ribbon. int endIndex = index + 1; while (endIndex < numberOfParticles && particles[endIndex].IsAlive) { endIndex++; } int numberOfSegments = endIndex - index - 1; var p0 = new RibbonArgs { // Uniform parameters Softness = particleSystemData.Softness, ReferenceAlpha = particleSystemData.AlphaTest }; var p1 = new RibbonArgs { // Uniform parameters Softness = particleSystemData.Softness, ReferenceAlpha = particleSystemData.AlphaTest }; // Compute axes and render ribbon. // First particle. if (requiresTransformation) { p0.Position = pose.ToWorldPosition(particles[index].Position * scale); p0.Size = particles[index].Size.Y * scale.Y; } else { p0.Position = particles[index].Position; p0.Size = particles[index].Size.Y; } p0.Color = particles[index].Color * color; p0.Alpha = particles[index].Alpha * alpha; p0.AnimationTime = particles[index].AnimationTime; p0.BlendMode = particles[index].BlendMode; p0.TextureCoordinateU = 0; index++; Vector3F nextPosition; if (requiresTransformation) { nextPosition = pose.ToWorldPosition(particles[index].Position * scale); } else { nextPosition = particles[index].Position; } Vector3F normal; if (uniformNormal.HasValue) { // Uniform normal. normal = uniformNormal.Value; } else { // Varying normal. normal = particles[index].Normal; if (requiresTransformation) { normal = pose.ToWorldDirection(normal); } } Vector3F previousDelta = nextPosition - p0.Position; p0.Axis = Vector3F.Cross(normal, previousDelta); p0.Axis.TryNormalize(); // Intermediate particles. while (index < endIndex - 1) { p1.Position = nextPosition; if (requiresTransformation) { nextPosition = pose.ToWorldPosition(particles[index + 1].Position * scale); p1.Size = particles[index].Size.Y * scale.Y; } else { nextPosition = particles[index + 1].Position; p1.Size = particles[index].Size.Y; } if (uniformNormal.HasValue) { // Uniform normal. normal = uniformNormal.Value; } else { // Varying normal. normal = particles[index].Normal; if (requiresTransformation) { normal = pose.ToWorldDirection(normal); } } Vector3F delta = nextPosition - p1.Position; Vector3F tangent = delta + previousDelta; // Note: Should we normalize vectors for better average? p1.Axis = Vector3F.Cross(normal, tangent); p1.Axis.TryNormalize(); p1.Color = particles[index].Color * color; p1.Alpha = particles[index].Alpha * alpha; p1.AnimationTime = particles[index].AnimationTime; p1.BlendMode = particles[index].BlendMode; p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling); // Draw ribbon segment. _billboardBatch.DrawRibbon(ref p0, ref p1, texture); p0 = p1; p0.TextureCoordinateU = GetTextureCoordinateU0(index, numberOfSegments, particleSystemData.TextureTiling); previousDelta = delta; index++; } // Last particle. p1.Position = nextPosition; if (uniformNormal.HasValue) { // Uniform normal. normal = uniformNormal.Value; } else { // Varying normal. normal = particles[index].Normal; if (requiresTransformation) { normal = pose.ToWorldDirection(normal); } } p1.Axis = Vector3F.Cross(normal, previousDelta); p1.Axis.TryNormalize(); if (requiresTransformation) { p1.Size = particles[index].Size.Y * scale.Y; } else { p1.Size = particles[index].Size.Y; } p1.Color = particles[index].Color * color; p1.Alpha = particles[index].Alpha * alpha; p1.AnimationTime = particles[index].AnimationTime; p1.BlendMode = particles[index].BlendMode; p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling); // Draw last ribbon segment. _billboardBatch.DrawRibbon(ref p0, ref p1, texture); index++; } while (index < numberOfParticles); }
// Particle ribbons: // Particles can be rendered as ribbons (a.k.a. trails, lines). Subsequent living // particles are connected using rectangles. // +--------------+--------------+ // | | | // p0 p1 p2 // | | | // +--------------+--------------+ // At least two living particles are required to create a ribbon. Dead particles // ("NormalizedAge" ≥ 1) can be used as delimiters to terminate one ribbon and // start the next ribbon. // // p0 and p1 can have different colors and alpha values to create color gradients // or a ribbon that fades in/out. private void DrawParticleRibbonsFixed(ParticleSystemData particleSystemData, bool requiresTransformation, ref Vector3F scale, ref Pose pose, ref Vector3F color, float alpha) { // At least two particles are required to create a ribbon. int numberOfParticles = particleSystemData.Particles.Count; if (numberOfParticles < 2) { return; } var particles = particleSystemData.Particles.Array; bool isAxisInViewSpace = particleSystemData.BillboardOrientation.IsAxisInViewSpace; int index = 0; do { // ----- Skip dead particles. while (index < numberOfParticles && !particles[index].IsAlive) { index++; } // ----- Start of new ribbon. int endIndex = index + 1; while (endIndex < numberOfParticles && particles[endIndex].IsAlive) { endIndex++; } int numberOfSegments = endIndex - index - 1; var p0 = new RibbonArgs { // Uniform parameters Softness = particleSystemData.Softness, ReferenceAlpha = particleSystemData.AlphaTest }; var p1 = new RibbonArgs { // Uniform parameters Softness = particleSystemData.Softness, ReferenceAlpha = particleSystemData.AlphaTest }; p0.Axis = particles[index].Axis; if (requiresTransformation) { p0.Position = pose.ToWorldPosition(particles[index].Position * scale); if (!isAxisInViewSpace) { p0.Axis = pose.ToWorldDirection(p0.Axis); } p0.Size = particles[index].Size.Y * scale.Y; } else { p0.Position = particles[index].Position; p0.Size = particles[index].Size.Y; } p0.Color = particles[index].Color * color; p0.Alpha = particles[index].Alpha * alpha; p0.AnimationTime = particles[index].AnimationTime; p0.BlendMode = particles[index].BlendMode; p0.TextureCoordinateU = 0; index++; while (index < endIndex) { p1.Axis = particles[index].Axis; if (requiresTransformation) { p1.Position = pose.ToWorldPosition(particles[index].Position * scale); if (!isAxisInViewSpace) { p1.Axis = pose.ToWorldDirection(p1.Axis); } p1.Size = particles[index].Size.Y * scale.Y; } else { p1.Position = particles[index].Position; p1.Size = particles[index].Size.Y; } p1.Color = particles[index].Color * color; p1.Alpha = particles[index].Alpha * alpha; p1.AnimationTime = particles[index].AnimationTime; p1.BlendMode = particles[index].BlendMode; p1.TextureCoordinateU = GetTextureCoordinateU1(index - 1, numberOfSegments, particleSystemData.TextureTiling); // Draw ribbon segment. var texture = particleSystemData.Texture ?? _debugTexture; _billboardBatch.DrawRibbon(ref p0, ref p1, texture); p0 = p1; p0.TextureCoordinateU = GetTextureCoordinateU0(index, numberOfSegments, particleSystemData.TextureTiling); index++; } } while (index < numberOfParticles); }
/// <summary> /// Adds a segment of a particle ribbon (4 vertices) to the vertex buffer. /// </summary> /// <param name="p0">The segment start.</param> /// <param name="p1">The segment end.</param> /// <param name="texture">The packed texture.</param> /// <param name="vertices">The vertex buffer.</param> /// <param name="index">The index of the next free slot in the vertex buffer.</param> protected abstract void OnDrawRibbon(ref RibbonArgs p0, ref RibbonArgs p1, PackedTexture texture, T[] vertices, int index);