/// <inheritdoc /> public override unsafe void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); var colorField = sorter.GetField(ParticleFields.Color); if (!colorField.IsValid()) { return; } var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } foreach (var particle in sorter) { // Set the vertex color attribute to the particle's color field var color = (uint)(*(Color4 *)particle[colorField]).ToRgba(); bufferState.SetAttributePerSegment(colAttribute, (IntPtr)(&color)); bufferState.NextSegment(); } bufferState.StartOver(); }
public override unsafe void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); // Update the non-default coordinates first, because they update off the default ones if (UVBuilder1 != null) { UVBuilder1.BuildUVCoordinates(ref bufferState, ref sorter, texCoord1); } // Update the default coordinates last if (UVBuilder0 != null) { UVBuilder0.BuildUVCoordinates(ref bufferState, ref sorter, texCoord0); } // If the particles have color field, the base class should have already passed the information if (HasColorField) { return; } // If there is no color stream we don't need to fill anything var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; bufferState.StartOver(); foreach (var particle in sorter) { bufferState.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); bufferState.NextParticle(); } bufferState.StartOver(); }
/// <inheritdoc /> public override unsafe void BuildUVCoordinates(ref ParticleBufferState bufferState, ref ParticleList sorter, AttributeDescription texCoordsDescription) { var lifeField = sorter.GetField(ParticleFields.RemainingLife); if (!lifeField.IsValid()) { return; } var texAttribute = bufferState.GetAccessor(texCoordsDescription); if (texAttribute.Size == 0 && texAttribute.Offset == 0) { return; } var texDefault = bufferState.GetAccessor(bufferState.DefaultTexCoords); if (texDefault.Size == 0 && texDefault.Offset == 0) { return; } foreach (var particle in sorter) { var normalizedTimeline = 1f - *(float *)(particle[lifeField]); var spriteId = startingFrame + (int)(normalizedTimeline * animationSpeedOverLife); Vector4 uvTransform = new Vector4((spriteId % xDivisions) * xStep, (spriteId / xDivisions) * yStep, xStep, yStep); bufferState.TransformAttributePerParticle(texDefault, texAttribute, this, ref uvTransform); bufferState.NextParticle(); } bufferState.StartOver(); }
/// <summary> /// Build the vertex buffer from particle data /// </summary> /// <param name="sharedBufferPtr">The shared vertex buffer position where the particle data should be output</param> /// <param name="invViewMatrix">The current camera's inverse view matrix</param> public void BuildVertexBuffer(IntPtr sharedBufferPtr, ref Matrix invViewMatrix, ref Matrix viewProj) { // Get camera-space X and Y axes for billboard expansion and sort the particles if needed var unitX = new Vector3(invViewMatrix.M11, invViewMatrix.M12, invViewMatrix.M13); var unitY = new Vector3(invViewMatrix.M21, invViewMatrix.M22, invViewMatrix.M23); // Not the best solution, might want to improve var depthVector = Vector3.Cross(unitX, unitY); if (simulationSpace == EmitterSimulationSpace.Local) { var inverseRotation = drawTransform.WorldRotation; inverseRotation.W *= -1; inverseRotation.Rotate(ref depthVector); } var sortedList = ParticleSorter.GetSortedList(depthVector); // If the particles are in world space they don't need to be fixed as their coordinates are already in world space // If the particles are in local space they need to be drawn in world space using the emitter's current location matrix var posIdentity = new Vector3(0, 0, 0); var rotIdentity = Quaternion.Identity; var scaleIdentity = 1f; if (simulationSpace == EmitterSimulationSpace.Local) { posIdentity = drawTransform.WorldPosition; rotIdentity = drawTransform.WorldRotation; scaleIdentity = drawTransform.WorldScale.X; } ParticleBufferState bufferState = new ParticleBufferState(sharedBufferPtr, VertexBuilder); ShapeBuilder.SetRequiredQuads(ShapeBuilder.QuadsPerParticle, pool.LivingParticles, pool.ParticleCapacity); ShapeBuilder.BuildVertexBuffer(ref bufferState, unitX, unitY, ref posIdentity, ref rotIdentity, scaleIdentity, ref sortedList, ref viewProj); bufferState.StartOver(); Material.PatchVertexBuffer(ref bufferState, unitX, unitY, ref sortedList); ParticleSorter.FreeSortedList(ref sortedList); }
/// <inheritdoc /> public unsafe override void BuildUVCoordinates(ref ParticleBufferState bufferState, ref ParticleList sorter, AttributeDescription texCoordsDescription) { var lifeField = sorter.GetField(ParticleFields.RemainingLife); if (!lifeField.IsValid()) { return; } var texAttribute = bufferState.GetAccessor(texCoordsDescription); if (texAttribute.Size == 0 && texAttribute.Offset == 0) { return; } var texDefault = bufferState.GetAccessor(bufferState.DefaultTexCoords); if (texDefault.Size == 0 && texDefault.Offset == 0) { return; } foreach (var particle in sorter) { var normalizedTimeline = 1f - *(float *)(particle[lifeField]); Vector4 uvTransform = Vector4.Lerp(StartFrame, EndFrame, normalizedTimeline); uvTransform.Z -= uvTransform.X; uvTransform.W -= uvTransform.Y; bufferState.TransformAttributePerSegment(texDefault, texAttribute, this, ref uvTransform); bufferState.NextSegment(); } bufferState.StartOver(); }
/// <inheritdoc /> public unsafe override void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can implement the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); // The UV Builder, if present, animates the basic (0, 0, 1, 1) uv coordinates of each billboard UVBuilder?.BuildUVCoordinates(ref bufferState, ref sorter, bufferState.DefaultTexCoords); bufferState.StartOver(); // If the particles have color field, the base class should have already passed the information if (HasColorField) { return; } // If the particles don't have color field but there is no color stream either we don't need to fill anything var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; // TODO: for loop. Remove IEnumerable from sorter foreach (var particle in sorter) { bufferState.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); bufferState.NextParticle(); } bufferState.StartOver(); }
public override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { return 0; }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter, ref Matrix viewProj) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter, ref viewProj); // Get all required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); } var lifeField = sorter.GetField(ParticleFields.Life); var sizeField = sorter.GetField(ParticleFields.Size); var rotField = sorter.GetField(ParticleFields.Quaternion); var hasRotation = rotField.IsValid() || (SamplerRotation != null); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var particleSize = GetParticleSize(particle, sizeField, lifeField); var unitX = new Vector3(1, 0, 0); var unitY = new Vector3(0, 0, 1); if (hasRotation) { var particleRotation = GetParticleRotation(particle, rotField, lifeField); particleRotation.Rotate(ref unitX); particleRotation.Rotate(ref unitY); } // The TRS matrix is not an identity, so we need to transform the quad if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; spaceRotation.Rotate(ref unitX); spaceRotation.Rotate(ref unitY); } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m unitX *= (particleSize * 0.5f); unitY *= (particleSize * 0.5f); var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <summary> /// Patch the particle's vertex buffer which was already built by the <see cref="ShapeBuilders.ShapeBuilder"/> /// This involes animating hte uv coordinates and filling per-particle fields, such as the color field /// </summary> /// <param name="bufferState">The particle buffer state which is used to build the assigned vertex buffer</param> /// <param name="invViewX">Unit vector X (right) in camera space, extracted from the inverse view matrix</param> /// <param name="invViewY">Unit vector Y (up) in camera space, extracted from the inverse view matrix</param> /// <param name="sorter">Particle enumerator which can be iterated and returns sported particles</param> public virtual void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sortedList) { }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); } var sizeField = sorter.GetField(ParticleFields.Size); var orderField = sorter.GetField(ParticleFields.Order); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors // ReSharper disable once CompareOfFloatsByEqualityOperator var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var ribbonizer = new Ribbonizer(this, currentTotalParticles, currentQuadsPerParticle); var renderedParticles = 0; bufferState.StartOver(); uint oldOrderValue = 0; foreach (var particle in sorter) { if (orderField.IsValid()) { var orderValue = (*((uint *)particle[orderField])); if ((orderValue >> SpawnOrderConst.GroupBitOffset) != (oldOrderValue >> SpawnOrderConst.GroupBitOffset)) { ribbonizer.Ribbonize(ref bufferState, invViewX, invViewY, QuadsPerParticle); ribbonizer.RibbonSplit(); } oldOrderValue = orderValue; } var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } ribbonizer.AddParticle(ref centralPos, particleSize); renderedParticles++; } ribbonizer.Ribbonize(ref bufferState, invViewX, invViewY, QuadsPerParticle); ribbonizer.Free(); var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <inheritdoc /> public unsafe override void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can implement the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); // The UV Builder, if present, animates the basic (0, 0, 1, 1) uv coordinates of each billboard UVBuilder?.BuildUVCoordinates(ref bufferState, ref sorter, bufferState.DefaultTexCoords); bufferState.StartOver(); // If the particles have color field, the base class should have already passed the information if (HasColorField) return; // If the particles don't have color field but there is no color stream either we don't need to fill anything var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) return; // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; // TODO: for loop. Remove IEnumerable from sorter foreach (var particle in sorter) { bufferState.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); bufferState.NextParticle(); } bufferState.StartOver(); }
/// <summary> /// Builds the actual vertex buffer for the current frame using the particle data /// </summary> /// <param name="bufferState">Target particle buffer state, used to populate the assigned vertex buffer</param> /// <param name="invViewX">Unit vector X (right) in camera space, extracted from the inverse view matrix</param> /// <param name="invViewY">Unit vector Y (up) in camera space, extracted from the inverse view matrix</param> /// <param name="spaceTranslation">Translation of the target draw space in regard to the particle data (world or local)</param> /// <param name="spaceRotation">Rotation of the target draw space in regard to the particle data (world or local)</param> /// <param name="spaceScale">Uniform scale of the target draw space in regard to the particle data (world or local)</param> /// <param name="sorter">Particle enumerator which can be iterated and returns sported particles</param> /// <returns></returns> public abstract int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter);
/// <summary> /// Enhances or animates the texture coordinates using already existing base coordinates of (0, 0, 1, 1) or similar /// (base texture coordinates may differ depending on the actual shape) /// </summary> /// <param name="bufferState">The particle buffer state which is used to build the assigned vertex buffer</param> /// <param name="sorter"><see cref="ParticleSorter"/> to use to iterate over all particles drawn this frame</param> /// <param name="texCoordsDescription">Attribute description of the texture coordinates in the current vertex layout</param> public abstract void BuildUVCoordinates(ref ParticleBufferState bufferState, ref ParticleList sorter, AttributeDescription texCoordsDescription);
public override unsafe void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); // Update the non-default coordinates first, because they update off the default ones if (UVBuilder1 != null) UVBuilder1.BuildUVCoordinates(ref bufferState, ref sorter, texCoord1); // Update the default coordinates last if (UVBuilder0 != null) UVBuilder0.BuildUVCoordinates(ref bufferState, ref sorter, texCoord0); // If the particles have color field, the base class should have already passed the information if (HasColorField) return; // If there is no color stream we don't need to fill anything var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) return; // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; bufferState.StartOver(); foreach (var particle in sorter) { bufferState.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); bufferState.NextParticle(); } bufferState.StartOver(); }
/// <inheritdoc /> public unsafe override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter); // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; var lifeField = sorter.GetField(ParticleFields.Life); var sizeField = sorter.GetField(ParticleFields.Size); var directionField = sorter.GetField(ParticleFields.Direction); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); Vector3 invViewZ; Vector3.Cross(ref invViewX, ref invViewY, out invViewZ); invViewZ.Normalize(); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var centralOffset = (directionField.IsValid()) ? particle.Get(directionField) : new Vector3(0, 1, 0); var particleSize = GetParticleSize(particle, sizeField, lifeField); if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; spaceRotation.Rotate(ref centralOffset); centralOffset = centralOffset * spaceScale; particleSize *= spaceScale; } var unitX = invViewX; var unitY = invViewY; { var centralAxis = centralOffset; float dotZ; Vector3.Dot(ref centralAxis, ref invViewZ, out dotZ); centralAxis -= invViewZ*dotZ; centralAxis.Normalize(); float dotX; Vector3.Dot(ref centralAxis, ref unitX, out dotX); float dotY; Vector3.Dot(ref centralAxis, ref unitY, out dotY); unitX = unitX*dotY - unitY*dotX; unitX.Normalize(); unitY = centralOffset; } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m unitX *= (particleSize * 0.5f); if (ScaleLength) { unitY *= (LengthFactor * particleSize * 0.5f); } else { unitY *= (LengthFactor * 0.5f); } var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
public override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter, ref Matrix viewProj) { return(0); }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter, ref Matrix viewProj) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter, ref viewProj); // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); } var lifeField = sorter.GetField(ParticleFields.Life); var sizeField = sorter.GetField(ParticleFields.Size); var directionField = sorter.GetField(ParticleFields.Direction); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); // TODO Use viewProj Vector3 invViewZ; Vector3.Cross(ref invViewX, ref invViewY, out invViewZ); invViewZ.Normalize(); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var centralOffset = (directionField.IsValid()) ? particle.Get(directionField) : new Vector3(0, 1, 0); var particleSize = GetParticleSize(particle, sizeField, lifeField); if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; spaceRotation.Rotate(ref centralOffset); centralOffset = centralOffset * spaceScale; particleSize *= spaceScale; } var unitX = invViewX; var unitY = invViewY; { var centralAxis = centralOffset; float dotZ; Vector3.Dot(ref centralAxis, ref invViewZ, out dotZ); centralAxis -= invViewZ * dotZ; centralAxis.Normalize(); float dotX; Vector3.Dot(ref centralAxis, ref unitX, out dotX); float dotY; Vector3.Dot(ref centralAxis, ref unitY, out dotY); unitX = unitX * dotY - unitY * dotX; unitX.Normalize(); unitY = centralOffset; } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m unitX *= (particleSize * 0.5f); if (ScaleLength) { unitY *= (LengthFactor * particleSize * 0.5f); } else { unitY *= (LengthFactor * 0.5f); } var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <summary> /// Constructs the ribbon by outputting vertex stream based on the positions and sizes specified previously /// </summary> /// <param name="bufferState">Target <see cref="ParticleBufferState"/></param> to use /// <param name="invViewX">Unit vector X in clip space as calculated from the inverse view matrix</param> /// <param name="invViewY">Unit vector Y in clip space as calculated from the inverse view matrix</param> /// <param name="quadsPerParticle">The required number of quads per each particle</param> public unsafe void Ribbonize(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, int quadsPerParticle) { if (lastParticle <= 0) return; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); if (lastParticle <= sections) { // Optional - connect first particle to the origin/emitter // Draw a dummy quad for the first particle var particlePos = new Vector3(0, 0, 0); var uvCoord = new Vector2(0, 0); for (var particleIdx = 0; particleIdx < lastParticle; particleIdx++) { for (var vtxIdx = 0; vtxIdx < 4; vtxIdx++) { bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); } } return; } if (sections > 1) { if (SmoothingPolicy == SmoothingPolicy.Best) ExpandVertices_Circular(); else // if (SmoothingPolicy == SmoothingPolicy.Fast) ExpandVertices_CatmullRom(); } bufferState.SetVerticesPerSegment(quadsPerParticle * 6, quadsPerParticle * 4, quadsPerParticle * 2); var positions = (Vector3*)positionData; var sizes = (float*)sizeData; // Step 1 - Determine the origin of the ribbon var invViewZ = Vector3.Cross(invViewX, invViewY); invViewZ.Normalize(); var axis0 = positions[0] - positions[1]; axis0.Normalize(); var oldPoint = positions[0]; var oldUnitX = GetWidthVector(sizes[0], ref invViewZ, ref axis0, ref axis0); // Step 2 - Draw each particle, connecting it to the previous (front) position var vCoordOld = 0f; for (int i = 0; i < lastParticle; i++) { var centralPos = positions[i]; var particleSize = sizes[i]; // Directions for smoothing var axis1 = (i + 1 < lastParticle) ? positions[i] - positions[i + 1] : positions[lastParticle - 2] - positions[lastParticle - 1]; axis1.Normalize(); var unitX = GetWidthVector(particleSize, ref invViewZ, ref axis0, ref axis1); axis0 = axis1; // Particle rotation - intentionally IGNORED for ribbon var particlePos = oldPoint - oldUnitX; var uvCoord = new Vector2(0, 0); var rotatedCoord = uvCoord; // Top Left - 0f 0f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 0 : vCoordOld; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Top Right - 1f 0f particlePos += oldUnitX * 2; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 1; rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Move the position to the next particle in the ribbon particlePos += centralPos - oldPoint; particlePos += unitX - oldUnitX; vCoordOld = (TextureCoordinatePolicy == TextureCoordinatePolicy.Stretched) ? ((i + 1)/(float)(lastParticle) * TexCoordsFactor) : ((centralPos - oldPoint).Length() * TexCoordsFactor) + vCoordOld; // Bottom Left - 1f 1f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 1 : vCoordOld; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Bottom Right - 0f 1f particlePos -= unitX * 2; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 0; rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Preserve the old attributes for the next cycle oldUnitX = unitX; oldPoint = centralPos; } }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; var sizeField = sorter.GetField(ParticleFields.Size); var orderField = sorter.GetField(ParticleFields.Order); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors // ReSharper disable once CompareOfFloatsByEqualityOperator var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var ribbonizer = new Ribbonizer(this, currentTotalParticles, currentQuadsPerParticle); var renderedParticles = 0; bufferState.StartOver(); uint oldOrderValue = 0; foreach (var particle in sorter) { if (orderField.IsValid()) { var orderValue = (*((uint*)particle[orderField])); if ((orderValue >> SpawnOrderConst.GroupBitOffset) != (oldOrderValue >> SpawnOrderConst.GroupBitOffset)) { ribbonizer.Ribbonize(ref bufferState, invViewX, invViewY, QuadsPerParticle); ribbonizer.RibbonSplit(); } oldOrderValue = orderValue; } var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } ribbonizer.AddParticle(ref centralPos, particleSize); renderedParticles++; } ribbonizer.Ribbonize(ref bufferState, invViewX, invViewY, QuadsPerParticle); ribbonizer.Free(); var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Step 1 - get all required fields to build the particle shapes. Some fields may not exist if no initializer or updater operates on them // In that case we just decide on a default value for that field and skip the update var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; // We can't display the particles without position. All other fields are optional var sizeField = sorter.GetField(ParticleFields.Size); var angleField = sorter.GetField(ParticleFields.Angle); var hasAngle = angleField.IsValid(); var rectField = sorter.GetField(CustomParticleFields.RectangleXY); var isRectangle = rectField.IsValid(); // In case of Local space particles they are simulated in local emitter space, but drawn in world space // If the draw space is identity (i.e. simulation space = draw space) skip transforming the particle's location later var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(new Quaternion(0, 0, 0, 1))); // Custom feature - fix the Y axis to always point up in world space rather than screen space if (FixYAxis) { invViewY = new Vector3(0, 1, 0); invViewX.Y = 0; invViewX.Normalize(); } var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } var unitX = invViewX * particleSize; var unitY = invViewY * particleSize; if (isRectangle) { var rectSize = particle.Get(rectField); unitX *= rectSize.X; unitY *= rectSize.Y; } // Particle rotation. Positive value means clockwise rotation. if (hasAngle) { var rotationAngle = particle.Get(angleField); var cosA = (float)Math.Cos(rotationAngle); var sinA = (float)Math.Sin(rotationAngle); var tempX = unitX * cosA - unitY * sinA; unitY = unitY * cosA + unitX * sinA; unitX = tempX; } var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } // Return the number of updated vertices var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter, ref Matrix viewProj) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter, ref viewProj); // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); } var lifeField = sorter.GetField(ParticleFields.Life); var sizeField = sorter.GetField(ParticleFields.Size); var angleField = sorter.GetField(ParticleFields.Angle); var hasAngle = angleField.IsValid() || (SamplerRotation != null); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(Vector3.Zero)); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var particleSize = GetParticleSize(particle, sizeField, lifeField); if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m var unitX = invViewX * (particleSize * 0.5f); var unitY = invViewY * (particleSize * 0.5f); // Particle rotation. Positive value means clockwise rotation. if (hasAngle) { var rotationAngle = GetParticleRotation(particle, angleField, lifeField); var cosA = (float)Math.Cos(rotationAngle); var sinA = (float)Math.Sin(rotationAngle); var tempX = unitX * cosA - unitY * sinA; unitY = unitY * cosA + unitX * sinA; unitX = tempX; } var particlePos = centralPos - unitX + unitY; var uvCoord = Vector2.Zero; // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <inheritdoc /> public unsafe override int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter); // Get all required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; var lifeField = sorter.GetField(ParticleFields.Life); var sizeField = sorter.GetField(ParticleFields.Size); var rotField = sorter.GetField(ParticleFields.Quaternion); var hasRotation = rotField.IsValid() || (SamplerRotation != null); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var particleSize = GetParticleSize(particle, sizeField, lifeField); var unitX = new Vector3(1, 0, 0); var unitY = new Vector3(0, 0, 1); if (hasRotation) { var particleRotation = GetParticleRotation(particle, rotField, lifeField); particleRotation.Rotate(ref unitX); particleRotation.Rotate(ref unitY); } // The TRS matrix is not an identity, so we need to transform the quad if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; spaceRotation.Rotate(ref unitX); spaceRotation.Rotate(ref unitY); } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m unitX *= (particleSize * 0.5f); unitY *= (particleSize * 0.5f); var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
/// <summary> /// Build the vertex buffer from particle data /// </summary> /// <param name="sharedBufferPtr">The shared vertex buffer position where the particle data should be output</param> /// <param name="invViewMatrix">The current camera's inverse view matrix</param> public void BuildVertexBuffer(IntPtr sharedBufferPtr, ref Matrix invViewMatrix) { // Get camera-space X and Y axes for billboard expansion and sort the particles if needed var unitX = new Vector3(invViewMatrix.M11, invViewMatrix.M12, invViewMatrix.M13); var unitY = new Vector3(invViewMatrix.M21, invViewMatrix.M22, invViewMatrix.M23); // Not the best solution, might want to improve var depthVector = Vector3.Cross(unitX, unitY); if (simulationSpace == EmitterSimulationSpace.Local) { var inverseRotation = drawTransform.WorldRotation; inverseRotation.W *= -1; inverseRotation.Rotate(ref depthVector); } var sortedList = ParticleSorter.GetSortedList(depthVector); // If the particles are in world space they don't need to be fixed as their coordinates are already in world space // If the particles are in local space they need to be drawn in world space using the emitter's current location matrix var posIdentity = new Vector3(0, 0, 0); var rotIdentity = Quaternion.Identity; var scaleIdentity = 1f; if (simulationSpace == EmitterSimulationSpace.Local) { posIdentity = drawTransform.WorldPosition; rotIdentity = drawTransform.WorldRotation; scaleIdentity = drawTransform.WorldScale.X; } ParticleBufferState bufferState = new ParticleBufferState(sharedBufferPtr, VertexBuilder); ShapeBuilder.SetRequiredQuads(ShapeBuilder.QuadsPerParticle, pool.LivingParticles, pool.ParticleCapacity); ShapeBuilder.BuildVertexBuffer(ref bufferState, unitX, unitY, ref posIdentity, ref rotIdentity, scaleIdentity, ref sortedList); bufferState.StartOver(); Material.PatchVertexBuffer(ref bufferState, unitX, unitY, ref sortedList); ParticleSorter.FreeSortedList(ref sortedList); }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter, ref Matrix viewProj) { // Step 1 - get all required fields to build the particle shapes. Some fields may not exist if no initializer or updater operates on them // In that case we just decide on a default value for that field and skip the update var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); // We can't display the particles without position. All other fields are optional } var sizeField = sorter.GetField(ParticleFields.Size); var angleField = sorter.GetField(ParticleFields.Angle); var hasAngle = angleField.IsValid(); var rectField = sorter.GetField(CustomParticleFields.RectangleXY); var isRectangle = rectField.IsValid(); // In case of Local space particles they are simulated in local emitter space, but drawn in world space // If the draw space is identity (i.e. simulation space = draw space) skip transforming the particle's location later var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(new Quaternion(0, 0, 0, 1))); // Custom feature - fix the Y axis to always point up in world space rather than screen space if (FixYAxis) { invViewY = new Vector3(0, 1, 0); invViewX.Y = 0; invViewX.Normalize(); } var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } var unitX = invViewX * particleSize; var unitY = invViewY * particleSize; if (isRectangle) { var rectSize = particle.Get(rectField); unitX *= rectSize.X; unitY *= rectSize.Y; } // Particle rotation. Positive value means clockwise rotation. if (hasAngle) { var rotationAngle = particle.Get(angleField); var cosA = (float)Math.Cos(rotationAngle); var sinA = (float)Math.Sin(rotationAngle); var tempX = unitX * cosA - unitY * sinA; unitY = unitY * cosA + unitX * sinA; unitX = tempX; } var particlePos = centralPos - unitX + unitY; var uvCoord = new Vector2(0, 0); // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= unitY * 2; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } // Return the number of updated vertices var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <inheritdoc /> public override unsafe int BuildVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ref ParticleList sorter) { // Update the curve samplers if required base.BuildVertexBuffer(ref bufferState, invViewX, invViewY, ref spaceTranslation, ref spaceRotation, spaceScale, ref sorter); // Get all required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; var sizeField = sorter.GetField(ParticleFields.Size); var lifeField = sorter.GetField(ParticleFields.Life); var angleField = sorter.GetField(ParticleFields.Angle); var hasAngle = angleField.IsValid() || (SamplerRotation != null); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); foreach (var particle in sorter) { var centralPos = GetParticlePosition(particle, positionField, lifeField); var particleSize = GetParticleSize(particle, sizeField, lifeField); if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } // Use half size to make a Size = 1 result in a Billboard of 1m x 1m var unitX = invViewX * (particleSize * 0.5f); var unitY = invViewY * (particleSize * 0.5f); // Particle rotation. Positive value means clockwise rotation. if (hasAngle) { var rotationAngle = GetParticleRotation(particle, angleField, lifeField); var cosA = (float)Math.Cos(rotationAngle); var sinA = (float)Math.Sin(rotationAngle); var tempX = unitX * cosA - unitY * sinA; unitY = unitY * cosA + unitX * sinA; unitX = tempX; } // vertex.Size = particleSize; const float Sqrt3Half = 0.86602540378f; unitY *= Sqrt3Half; var halfX = unitX * 0.5f; var particlePos = centralPos - halfX + unitY; var uvCoord = new Vector2(0.25f, 0.5f - Sqrt3Half * 0.5f); // Upper half // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX; uvCoord.X = 0.75f; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos += halfX; particlePos -= unitY; uvCoord.X = 1; uvCoord.Y = 0.5f; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX * 2; uvCoord.X = 0; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // Upper half // 0f 0f bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 0f particlePos += unitX * 2; uvCoord.X = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 1f 1f particlePos -= halfX; particlePos -= unitY; uvCoord.X = 0.75f; uvCoord.Y = 1; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); // 0f 1f particlePos -= unitX; uvCoord.X = 0.25f; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); renderedParticles++; } var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
/// <summary> /// Constructs the ribbon by outputting vertex stream based on the positions and sizes specified previously /// </summary> /// <param name="bufferState">Target <see cref="ParticleBufferState"/></param> to use /// <param name="invViewX">Unit vector X in clip space as calculated from the inverse view matrix</param> /// <param name="invViewY">Unit vector Y in clip space as calculated from the inverse view matrix</param> /// <param name="quadsPerParticle">The required number of quads per each particle</param> public unsafe void Ribbonize(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, int quadsPerParticle) { if (lastParticle <= 0) { return; } var posAttribute = bufferState.GetAccessor(VertexAttributes.Position); var texAttribute = bufferState.GetAccessor(bufferState.DefaultTexCoords); if (lastParticle <= sections) { // Optional - connect first particle to the origin/emitter // Draw a dummy quad for the first particle var particlePos = new Vector3(0, 0, 0); var uvCoord = new Vector2(0, 0); for (var particleIdx = 0; particleIdx < lastParticle; particleIdx++) { for (var vtxIdx = 0; vtxIdx < 4; vtxIdx++) { bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); bufferState.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); bufferState.NextVertex(); } } return; } if (sections > 1) { if (SmoothingPolicy == SmoothingPolicy.Best) { ExpandVertices_Circular(); } else // if (SmoothingPolicy == SmoothingPolicy.Fast) { ExpandVertices_CatmullRom(); } } bufferState.SetVerticesPerSegment(quadsPerParticle * 6, quadsPerParticle * 4, quadsPerParticle * 2); var positions = (Vector3 *)positionData; var sizes = (float *)sizeData; // Step 1 - Determine the origin of the ribbon var invViewZ = Vector3.Cross(invViewX, invViewY); invViewZ.Normalize(); var axis0 = positions[0] - positions[1]; axis0.Normalize(); var oldPoint = positions[0]; var oldUnitX = GetWidthVector(sizes[0], ref invViewZ, ref axis0, ref axis0); // Step 2 - Draw each particle, connecting it to the previous (front) position var vCoordOld = 0f; for (int i = 0; i < lastParticle; i++) { var centralPos = positions[i]; var particleSize = sizes[i]; // Directions for smoothing var axis1 = (i + 1 < lastParticle) ? positions[i] - positions[i + 1] : positions[lastParticle - 2] - positions[lastParticle - 1]; axis1.Normalize(); var unitX = GetWidthVector(particleSize, ref invViewZ, ref axis0, ref axis1); axis0 = axis1; // Particle rotation - intentionally IGNORED for ribbon var particlePos = oldPoint - oldUnitX; var uvCoord = new Vector2(0, 0); var rotatedCoord = uvCoord; // Top Left - 0f 0f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 0 : vCoordOld; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Top Right - 1f 0f particlePos += oldUnitX * 2; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 1; rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Move the position to the next particle in the ribbon particlePos += centralPos - oldPoint; particlePos += unitX - oldUnitX; vCoordOld = (TextureCoordinatePolicy == TextureCoordinatePolicy.Stretched) ? ((i + 1) / (float)(lastParticle) * TexCoordsFactor) : ((centralPos - oldPoint).Length() * TexCoordsFactor) + vCoordOld; // Bottom Left - 1f 1f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 1 : vCoordOld; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Bottom Right - 0f 1f particlePos -= unitX * 2; bufferState.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 0; rotatedCoord = UVRotate.GetCoords(uvCoord); bufferState.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); bufferState.NextVertex(); // Preserve the old attributes for the next cycle oldUnitX = unitX; oldPoint = centralPos; } }
/// <inheritdoc /> public override unsafe void PatchVertexBuffer(ref ParticleBufferState bufferState, Vector3 invViewX, Vector3 invViewY, ref ParticleList sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(ref bufferState, invViewX, invViewY, ref sorter); var colorField = sorter.GetField(ParticleFields.Color); if (!colorField.IsValid()) return; var colAttribute = bufferState.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) return; foreach (var particle in sorter) { // Set the vertex color attribute to the particle's color field var color = (uint)(*(Color4*)particle[colorField]).ToRgba(); bufferState.SetAttributePerSegment(colAttribute, (IntPtr)(&color)); bufferState.NextSegment(); } bufferState.StartOver(); }