using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ChiseDrive;
using ChiseDrive.Units;
using ChiseDrive.Graphics;
using ChiseDrive.Particles;
namespace SamuraiVsZombie.Players
{
/// <summary>
/// This system tracks bone accuracy impacts from weapons and then
/// creates a bleeding effect from the injured character.
/// </summary>
public class BleedSystem
{
/// <summary>
/// A nested BleedEffect class, contained by the BleedSystem.
/// </summary>
public class BleedEffect : IFollow
{
/// <summary>
/// The character that the bone comes from
/// </summary>
GameModel Skeleton;
/// <summary>
/// The name of the particular bone
/// </summary>
string BoneName;
/// <summary>
/// The distance down the bone to travel
/// </summary>
float BoneOffset;
/// <summary>
/// The direction the blood is spraying in
/// </summary>
Vector3 BloodSpray;
/// <summary>
/// The particle emitter that bleeds
/// </summary>
ParticleEmitter BloodEmitter;
/// <summary>
/// Is this bleed effect finished?
/// </summary>
public bool IsDone
{
get
{
return BloodEmitter.IsDone;
}
}
#region IFollow – Position, Velocity, Rotation
Vector3 position;
public Vector3 Position
{
get
{
return position;
}
set
{
throw new NotImplementedException();
}
}
public Vector3 Velocity
{
get
{
Matrix bone = Skeleton.BoneTransform(BoneName);
bone.Forward += BloodSpray;
return bone.Forward;
}
}
public Matrix RotationPosition
{
get
{
Matrix bone = Skeleton.BoneTransform(BoneName);
bone.Translation += bone.Forward * BoneOffset;
return bone;
}
}
public Quaternion Rotation
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public float Scale
{
get
{
return 1f;
}
}
#endregion
/// <summary>
/// Creates a new bleed effect.
/// </summary>
/// <param name=“emitterSettings“>The settings to use for setting
/// up a ParticleEmitter</param>
public BleedEffect(ParticleEmitterSettings emitterSettings)
{
BloodEmitter = new ParticleEmitter(this, emitterSettings);
}
public void Dispose()
{
// members
BloodEmitter.Dispose();
BloodEmitter = null;
// references
Skeleton = null;
}
public void Reset()
{
BloodEmitter.Reset();
}
/// <summary>
/// Starts a new blood spray effect
/// </summary>
/// <param name=“skeleton“>The skeleton it’s attached to</param>
/// <param name=“bone“>The bone it’s attached to</param>
/// <param name=“offset“>The distance down the bone</param>
/// <param name=“direction“>The direction to spray</param>
/// <param name=“time“>The time to spray</param>
public void NewBleed(GameModel skeleton,
string bone,
float offset,
Vector3 direction)
{
Skeleton = skeleton;
BoneName = bone;
BoneOffset = offset;
BloodSpray = direction;
BloodEmitter.EmitOnce();
}
/// <summary>
/// Updates the position/rotation/emitter.
/// </summary>
/// <param name=“elapsed“>Time elapsed.</param>
public void Update(Time elapsed)
{
Matrix bone = Skeleton.BoneTransform(BoneName);
bone *= Skeleton.RotationPosition;
position = bone.Translation;
position += bone.Up * BoneOffset * Skeleton.Scale;
BloodEmitter.Update(elapsed);
}
}
/// <summary>
/// Up to 200 blood sprays can be simultaneously occuring.
/// </summary>
BleedEffect[] BleedEffects = new BleedEffect[200];
int ActiveBleeds = 0;
public BleedSystem(SamuraiZombie Game)
{
ParticleEmitterSettings settings =
Game.Content.Load<ParticleEmitterSettings>(
“Particles/BleedEmitter“);
for (int i = 0; i < BleedEffects.Length; i++)
{
BleedEffects[i] = new BleedEffect(settings);
}
}
public void Dispose()
{
for (int i = 0; i < BleedEffects.Length; i++)
{
BleedEffects[i].Dispose();
BleedEffects[i] = null;
}
BleedEffects = null;
ActiveBleeds = 0;
}
public void ResetAll()
{
foreach (BleedEffect bleed in BleedEffects)
{
bleed.Reset();
}
}
public override string ToString()
{
return “BleedSystem“;
}
/// <summary>
/// Blade to Bone accuracy requires a clean bone cut to start a
/// bleed effect.
/// </summary>
/// <param name=“bladeEdge“>A blade edge to test against.</param>
/// <param name=“skeleton“>The skeleton of the character to
/// test.</param>
/// <returns>The point of impact or null.</returns>
public Vector3? Slash(
BladeEdge bladeEdge,
GameModel skeleton)
{
// This is a bounding box that contains the full sweep
// that this sword has gone through in this frame.
BoundingBox bladeSweep = bladeEdge.Area;
// Check to see if at some point in this frame this
// sword might have intersected the skeleton.
// This is a relatively fast operation, and if it
// fails to find an intersect, we don‘t waste time
// with all the intense computations inside the statement.
if (skeleton.BoundingBox.Contains(bladeSweep)
== ContainmentType.Intersects)
{
foreach (string name in skeleton.GetBoneNames())
{
// skeleton.BoneTransform returns a skinned
// transform that‘s relative to the skeleton
// heiarchy, but we need global relativity
// so apply the skeleton‘s RotationPosition.
Matrix bone = skeleton.BoneTransform(name)
* skeleton.RotationPosition;
float boneLength = skeleton.BoneLength(name);
// Check this bone.
float? intersect = CheckSingleBone(
ref bone,
ref bladeEdge,
ref boneLength);
if (intersect != null)
{
// The blade sliced through this bone
Ray test = new Ray(
bone.Translation,
bone.Up);
// We want the blood to travel in
// the opposite direction that this
// blade is traveling in.
Vector3 bleedDirection =
–bladeEdge.Direction;
return StartBleed(
skeleton,
name,
test,
(float)intersect,
bleedDirection);
}
}
}
return null;
}
/// <summary>
/// When precise slashes aren’t working well enough, try
/// this more forgiving method that fudges the numbers
/// to find a bone that was almost hit by the blade.
/// </summary>
/// <param name=“bladeEdge“>A blade to test against.</param>
/// <param name=“skeleton“>A skeleton to test.</param>
/// <param name=“accuracy“>0 for only true strikes,
/// larger values for more generous fudging.</param>
/// <returns>The point of impact or null.</returns>
public Vector3? SlashNearest(
BladeEdge bladeEdge,
GameModel skeleton,
float accuracy)
{
// The same simple discard for non–intersection as above.
if (skeleton.BoundingBox.Contains(bladeEdge.Area)
== ContainmentType.Intersects)
{
// Find the center and base of the blade arc
Vector3 bladeCenter = bladeEdge.Center;
Vector3 bladeBase = bladeEdge.Base;
float closestDistance = float.MaxValue;
Matrix closestBone = Matrix.Identity;
string closestName = string.Empty;
foreach (string name in skeleton.GetBoneNames())
{
// skeleton.BoneTransform returns a skinned
// transform that‘s relative to the skeleton
// heiarchy, but we need global relativity
// so apply the skeleton‘s RotationPosition.
Matrix bone = skeleton.BoneTransform(name)
* skeleton.RotationPosition;
float boneLength = skeleton.BoneLength(name);
// Check this bone.
float? intersect = CheckSingleBone(
ref bone,
ref bladeEdge,
ref boneLength);
// If we have a true intersect break the loop
if (intersect != null)
{
closestDistance = 0f;
closestBone = bone;
closestName = name;
break;
}
// Find out how close we came to the bone,
// calculated against the base and center,
// but excluding the tip.
float distance1 = Vector3.Distance(
bladeBase, bone.Translation);
float distance2 = Vector3.Distance(
bladeCenter, bone.Translation);
float distance =
distance1 < distance2 ? distance1 : distance2;
// Remember the bone we were the closest to.
if (distance < closestDistance)
{
closestDistance = distance;
closestBone = bone;
closestName = name;
}
}
if (closestDistance <= accuracy)
{
// We almost slashed this bone, which is
// good enough for the fudge method.
Ray bone = new Ray(
closestBone.Translation,
closestBone.Up);
// We want the blood to travel in
// the opposite direction that this
// blade is traveling in.
Vector3 bleedDirection =
–bladeEdge.Direction;
return StartBleed(
skeleton,
closestName,
bone,
closestDistance,// possibly out of the mesh
bleedDirection);
}
}
return null;
}
/// <summary>
/// Checks a single bone for a clean cut. Private,
/// as it uses references to data to keep a lean
/// efficient inner loop.
/// </summary>
/// <param name=“bone“></param>
/// <param name=“bladeEdge“></param>
/// <param name=“boneLength“></param>
/// <returns></returns>
float? CheckSingleBone(
ref Matrix bone,
ref BladeEdge bladeEdge,
ref float boneLength)
{
Ray test = new Ray(
bone.Translation, // Position
bone.Up); // Direction
// Check to see if the bone intersects the
// sword‘s plane, and if that intersection
// occurs within our bone‘s length
float? intersect = test.Intersects(bladeEdge.Plane);
if (intersect != null
&& (float)intersect < boneLength)
{
// Now move on to very expensive triangle collision
Helper.RayIntersectsTriangle(
ref test,
ref bladeEdge.First.Point1,
ref bladeEdge.First.Point2,
ref bladeEdge.Second.Point1,
out intersect);// Will set intersect to null if fails
if (intersect == null)
{
// Second triangle
Helper.RayIntersectsTriangle(
ref test,
ref bladeEdge.Second.Point1,
ref bladeEdge.Second.Point2,
ref bladeEdge.First.Point1,
out intersect);// Will set intersect to null if fails
}
}
else
{
// The intersect was greater than the bone length,
// so set it to null.
intersect = null;
}
// May be null!
return intersect;
}
/// <summary>
/// Starts a bleed effect.
/// </summary>
/// <param name=“skeleton“>The skeleton to attach to.</param>
/// <param name=“name“>The bone name.</param>
/// <param name=“bone“>A Ray representing the bone.</param>
/// <param name=“intersect“>The intersect point along
/// the ray.</param>
/// <param name=“direction“>The direction of the impact</param>
/// <returns>The point on the skeleton.</returns>
Vector3 StartBleed(
GameModel skeleton,
string name,
Ray bone,
float intersect,
Vector3 direction)
{
if (ActiveBleeds < BleedEffects.Length)
{
Vector3 returnvalue = bone.Position;
returnvalue += bone.Direction * (float)intersect;
// A safe normalize that will catch corrupted
// data in DEBUG mode
Helper.Normalize(ref direction);
// Start the actual bleed effect.
BleedEffects[ActiveBleeds].NewBleed(skeleton, name,
(float)intersect, direction);
ActiveBleeds++;
return returnvalue;
}
return Vector3.Zero;
}
/// <summary>
/// Compacts bleeds.
/// </summary>
void CompactBleeds()
{
if (ActiveBleeds == 0) return;
for (int i = 0; i < BleedEffects.Length; i++)
{
if (BleedEffects[i].IsDone)
{
// Assume this expired bleed is our last one!
ActiveBleeds = i;
for (int j = ActiveBleeds; j <= BleedEffects.Length; j++)
{
// We‘ve looked through all the rest
// and found nothing to compact, so escape
// from the double nested for loops
if (j == BleedEffects.Length) return;
if (!BleedEffects[j].IsDone)
{
// This one is active! Move it up in the list!
BleedEffect temp = BleedEffects[i];
BleedEffects[i] = BleedEffects[j];
BleedEffects[j] = temp;
break;// for j <= BleedEffects.Length
}
}
}
}
}
/// <summary>
/// Updates all bleed effects and compacts them when done.
/// </summary>
/// <param name=“elapsed“>Simulation time to apply.</param>
public void Update(Time elapsed)
{
for (int i = 0; i < ActiveBleeds; i++)
{
BleedEffects[i].Update(elapsed);
}
CompactBleeds();
}
}
}