A helping hand for bedroom coders throughout the land.
Light Cycles, Ribbons and Windows Phone 7

Blogs

RandomChaos

Syndication

I put a clip up the other day of the ribbon emitter I was playing with, so here is the code and blog post as promised.

The idea behind the ribbon emitter was to be able to lay down a length of connected particles, normally when we use a regular particle emitter if the velocity of the emitter is quite high you get gaps in the particles emitted rather than the nice particle stream you get at lower velocities. I guess you could compensate for this by lerping from the last particle laid to the emitters current position and then loading the path with particles in-between, but this would add more calculations and vertices to the render, a solution, but not the best.

With the ribbon emitter I have currently written, as the emitter moves I just bind to the end 2 vertices and move those with the emitter, if the emitter changes direction, I add another 4 vertices (a quad) to the end and bind the end two again to the emitter. Here is a clip of the ribbon in wireframe showing the first stages of the ribbon

image

 

As you can see, there are just 4 vertices used so far, when the cycles change direction, the old vertices are left behind and 4 new ones are added, and the last two are picked up again

 

image

 

If you run the solution in windows and hit ‘P’ to pause it, zoom up to the selected cycle you can use the keypad keys to rotate and translate the cycle, doing this will result in more of the sections being laid as the cycle does a tight turn like this:

 

image 

And here is is in wire frame so you can see them better:

 

image

It’s really as simple as that, only thing I have not implemented  is a life span for each of the sections (quads), but I am sure you don’t want me writing everything for you do you… So, lets look at the code.

Ribbon Class

I’ll start with the ribbon class, all it is really is a manager for the vertex array, so we have just that, in this case I decided to use the VertexPositionNormalTexture intrinsic XNA vertex element. I then created a method called AddSection, which accepts a Vector3 and a Quaternion for rotation. The new quad will start at the position of the last end vertices in the last quad and the last two of the new quad will be bound to the emitter.

        public void AddSection(Vector3 position,Quaternion rotation)
        {
            Vector3 TopStart = position + ((Vector3.Up + Vector3.Left) * .5f);
            Vector3 TopEnd = position + ((Vector3.Up + Vector3.Right) * .5f);
            Vector3 BottomStart = position + ((Vector3.Down + Vector3.Left) * .5f);
            Vector3 BottomEnd = position + ((Vector3.Down + Vector3.Right) * .5f);
            
            VertexPositionNormalTexture[] vertsOld = verts;
            short[] indexOld = index;

            TopEnd = Vector3.Transform(TopEnd, Matrix.CreateFromQuaternion(rotation));
            BottomEnd = Vector3.Transform(BottomEnd, Matrix.CreateFromQuaternion(rotation));

            if (vertsOld != null)
            {
                verts = new VertexPositionNormalTexture[vertsOld.Length + 4];
                vertsOld.CopyTo(verts, 0);
                
                BottomStart = vertsOld[vertsOld.Length - 4].Position;
                TopStart = vertsOld[vertsOld.Length-1].Position;

                // Bottom Right
                verts[vertsOld.Length + 0] = new VertexPositionNormalTexture();
                verts[vertsOld.Length + 0].Position = BottomEnd;
                // Bottom Left
                verts[vertsOld.Length + 1] = new VertexPositionNormalTexture();
                verts[vertsOld.Length + 1].Position = BottomStart;
                // Top Left
                verts[vertsOld.Length + 2] = new VertexPositionNormalTexture();
                verts[vertsOld.Length + 2].Position = TopStart;
                // Top Right
                verts[vertsOld.Length + 3] = new VertexPositionNormalTexture();
                verts[vertsOld.Length + 3].Position = TopEnd;

                index = new short[indexOld.Length + 6];
                indexOld.CopyTo(index, 0);
            }
            else
            {
                verts = new VertexPositionNormalTexture[4];

                // Bottom Right
                verts[0] = new VertexPositionNormalTexture();
                verts[0].Position = BottomEnd;
                // Bottom Left
                verts[1] = new VertexPositionNormalTexture();
                verts[1].Position = BottomStart;
                // Top Left
                verts[2] = new VertexPositionNormalTexture();
                verts[2].Position = TopStart;
                // Top Right
                verts[3] = new VertexPositionNormalTexture();
                verts[3].Position = TopEnd;

                index = new short[6];
            }

            // Normals
            verts[verts.Length - 4].Normal = Vector3.Normalize(Vector3.Cross(verts[verts.Length - 4].Position - verts[verts.Length - 1].Position, verts[verts.Length - 4].Position - verts[verts.Length - 3].Position));
            verts[verts.Length - 3].Normal = Vector3.Normalize(Vector3.Cross(verts[verts.Length - 3].Position - verts[verts.Length - 4].Position, verts[verts.Length - 3].Position - verts[verts.Length - 2].Position));
            verts[verts.Length - 2].Normal = Vector3.Normalize(Vector3.Cross(verts[verts.Length - 2].Position - verts[verts.Length - 3].Position, verts[verts.Length - 2].Position - verts[verts.Length - 1].Position));
            verts[verts.Length - 1].Normal = Vector3.Normalize(Vector3.Cross(verts[verts.Length - 1].Position - verts[verts.Length - 2].Position, verts[verts.Length - 1].Position - verts[verts.Length - 4].Position));

            // Texcooreds
            verts[verts.Length - 4].TextureCoordinate = new Vector2(1, 1);
            verts[verts.Length - 3].TextureCoordinate = new Vector2(0, 1);
            verts[verts.Length - 2].TextureCoordinate = new Vector2(0, 0);
            verts[verts.Length - 1].TextureCoordinate = new Vector2(1, 0);

            if (indexOld != null)
            {
                for (int i = 0; i < indexBlock.Length; i++)
                {
                    index[(indexOld.Length) + i] = (short)(indexBlock[i] + (4 * (Count - 1)));
                }                
            }
            else
                index = indexBlock;

            vb = new DynamicVertexBuffer(Game.GraphicsDevice, typeof(VertexPositionNormalTexture), verts.Length, BufferUsage.WriteOnly);
            vb.SetData(verts);
        }

 

 

 

A chunky function, but all it is really doing is adding 4 new vertices to the vertex array each time it’s called, first it copies any old vertices into the old array, extends the vertex array by 4 for the new quad, then sets the position of the two starting points of each quad so it is in the same place as the ends of vertices in the old list, then updates the index array accordingly. Once the new vertex positions are in the array I then calculate their normals and then set their tex coords, then reset the vertex buffer.

The class also has Top and Bottom fields to denote where the leading edge’s top and bottom vertices should be, so in the Update method I ensure that the last two vertices in the array are at the corresponding positions.

        public override void Update(GameTime gameTime)
        {
#if WINDOWS
            effect.Parameters["world"].SetValue(Matrix.Identity);
            effect.Parameters["wvp"].SetValue(Matrix.Identity * camera.View * camera.Projection);
            effect.Parameters["CameraPosition"].SetValue(camera.Position);            
#endif
#if WINDOWS_PHONE
            effect.World = Matrix.Identity;
            effect.View = camera.View;
            effect.Projection = camera.Projection;
            effect.AmbientLightColor = AmbientColor.ToVector3();
            effect.DiffuseColor = DiffuseColor.ToVector3();
            effect.DirectionalLight0.Direction = Vector3.Left;
            effect.LightingEnabled = true;
            effect.Projection = camera.Projection;
            effect.SpecularPower = 35;
           
#endif
            if (verts != null)
            {
                verts[verts.Length - 1].Position = Top;
                verts[verts.Length - 4].Position = Bottom;
            }
        }

Then comes the Draw method which is pretty strait forward really

 

        public override void Draw(GameTime gameTime)
        {
            if (vb != null)
            {
                Game.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
                Game.GraphicsDevice.BlendState = BlendState.Opaque;
                Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
#if WINDOWS
                //Game.GraphicsDevice.DepthStencilState = DepthStencilState.None;
                effect.Parameters["DiffuseColor"].SetValue(DiffuseColor.ToVector4());
                effect.Parameters["AmbientColor"].SetValue(AmbientColor.ToVector4());
#endif
                Game.GraphicsDevice.SetVertexBuffer(vb);

                effect.CurrentTechnique.Passes[0].Apply();

                Game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, verts, 0, verts.Length, index, 0, verts.Length / 2);
            }
        }

 

And that’s the ribbon class.

HUD Controls

I created the HUD controls so that on the phone you can control the camera, what cycle you are looking at and so on, this was pretty strait forward to do, first of all creating a set of fields so I can render the sprites to the screen

        public int Top;
        public int Left;
        public int Width;
        public int Height;
        public Color Tint;
        public string Sprite;

        Texture2D texture;
        Vector2 Origin;
        Rectangle target;
        Rectangle source;
        Rectangle bounds;

        public float Rotation;

Then adding in some fields to manage touches and mouse clicks

        public string Name;

        public ClickEvent OnClick;
        Rectangle HIRec;
        MouseState lastState;

        public bool FullClick = true;

#if WINDOWS_PHONE
        TouchCollection touches;
        int clickID = 0;
#endif

 

I also made the spriteBatch in the Game1.cs a public field so I could access it in here too, as well as set up the constructor so that elements got set and ensured the sprite is always drawn last, or has a very high draw order at the very least

        private SpriteBatch spriteBatch
        {
            get { return ((Game1)Game).spriteBatch; }
        }

        public HUDButton(Game game,string sprite,string name, int left, int top, int width, int height, Color tint,float rotation) : base(game)
        {
            Top = top;
            Left = left;
            Width = width;
            Height = height;
            Tint = tint;
            Sprite = sprite;
            Rotation = rotation;
            Name = name;

            // Always want to draw the hud last.
            DrawOrder = 9999;
        }

When loading the content I calculate the various elements needed for the sprite, click bounds, it’s origin etc.

        protected override void LoadContent()
        {
            base.LoadContent();

            texture = Game.Content.Load<Texture2D>(Sprite);
            Origin = new Vector2(texture.Width, texture.Height) * .5f;
            target = new Rectangle(Left + Width / 2, Top + Height / 2, Width, Height);
            bounds = new Rectangle(Left, Top, Width, Height);
            source = new Rectangle(0, 0, texture.Width, texture.Height);
        }

The update method simply manages the click and touch events

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
#if WINDOWS_PHONE
                touches = TouchPanel.GetState();
                if (touches.Count == 0)
                    clickID = -1;
                foreach (TouchLocation tl in touches)
                {
                    HIRec = new Rectangle((int)tl.Position.X, (int)tl.Position.Y, 16, 16);

                    if (tl.State != TouchLocationState.Released && tl.State != TouchLocationState.Invalid && clickID != tl.Id)
                    {
                        if(FullClick)
                            clickID = tl.Id;
#endif
#if WINDOWS
            MouseState ms = Mouse.GetState();
            bool clicked = false;

            if (!FullClick)
            {
                if (ms.LeftButton == ButtonState.Pressed)
                    clicked = true;
            }
            else
            {
                if (ms.LeftButton == ButtonState.Pressed && lastState.LeftButton == ButtonState.Released)
                    clicked = true;
            }

            if (clicked)
            {
                HIRec = new Rectangle(ms.X, ms.Y, 1, 1);
#endif
                if (bounds.Intersects(HIRec))
                {
                    if (OnClick != null)
                    {
                        OnClick(this);
                    }
                }
#if WINDOWS_PHONE
                    }
#endif
            }
#if WINDOWS
            lastState = ms;
#endif
        }

And again a very simple draw call

        public override void Draw(GameTime gameTime)
        {
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
            spriteBatch.Draw(texture, target, source, Tint, Rotation, Origin, SpriteEffects.None, 1);
            spriteBatch.End();
        }

Other Objects

As you will see I have my regular objects in this solution for the camera and to render a 3D object, so I wont bother going into those :D

In “Game”

In the constructor I just create 4 instances of the ribbon class along with 4 cycles to be used as the emitters, as well as create 4 sets of paths for the cycles so they can go from place to place, then set up the HUD and wire up the events for the buttons and that’s about it…

I feel a TRON game is a MUST do now, just need to find the time to do it :P

NOTE:

I have not applied a texture to the ribbon, but I am sure it’s pretty obvious that it will display an image per quad, and also stretch it as the quad elongates, this too can be compensated for, but this implementation doesn’t.

Down load the source project here


Posted Fri, Aug 6 2010 3:55 PM by Charles Humphrey

Comments

Community Blogs wrote Windows Client Developer Roundup 036 for 8/9/2010
on Mon, Aug 9 2010 11:59 PM

This is Windows Client Developer roundup #36. The Windows Client Developer Roundup aggregates information

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Fri, Dec 3 2010 9:14 PM

hi there, this project doesn't open in the Express Edition of C#.  which version did you save it under?  thanks.

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sat, Dec 4 2010 8:10 AM

Ultimate, but I am sure it does not matter.... Are you using VS 2010?

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sun, Dec 5 2010 3:43 AM

i'm using Visual C# 2008 Express Edition.  i'm guessing i need to upgrade.  i'm really looking forward to running your code.

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sun, Dec 5 2010 9:59 AM

Good, hope you like it :)

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Jan 19 2011 2:49 AM

hey there, i just upgraded to XNA 4.0 and Visual C# 2010 (and Sunburn 2.0) and although your ribbon sample is running fine, when i integrate it into my game, the ribbons don't render.  the logic is working and the arrays are being filled in, but the ribbon's just aren't showing.

would you have any quick tips i should check for?  this in on the Windows build.  thanks!

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Jan 19 2011 8:56 AM

First off, is the draw call being made?

If it is is culling off when the ribbons are drawn, I do this so that both sides of the ribbon can be seen.

Without seeing your code, it's hard to tell what is up.

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Jan 19 2011 4:54 PM

yes, the Draw call is being made.  in fact, just about every line of your code (unaltered) is being executed - i stepped through each line and the results look correct.  problem is, nothing's showing up.

it's obviously something on my end cause your sample-demo works great.

i'm guessing it has to do with how i integrated it within the latest version of Sunburn.  i'll keep digging - thanks.

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Thu, Jan 20 2011 9:30 AM

Ahh, integrating it with Sun Burn eh... well I don't know much a bout SB, other than it is deferred lighting and the ribbon code I have put up is a regular forward render using the intrinsic XNA shaders.

I think you will need to do some work to get it working with SB.

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sun, Jan 23 2011 5:26 PM

i'm actually using SB in forward rendering mode.  thanks, i'll beat it into submission!

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sun, Jan 23 2011 7:34 PM

OK, well good luck :)

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Jan 26 2011 8:34 PM

hi again - i had one more question.  do you think your code is a good basis to get engine trails that look like the link below working?

tefearimpor.com/.../enginetrails.html

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Jan 26 2011 8:45 PM

Yep, that's how I intend to use them, need the right textures though :)

eluder wrote re: Light Cycles, Ribbons and Windows Phone 7
on Wed, Feb 9 2011 10:16 PM

hey there.

i was finally able to get these ribbons working in my game and they look great!  i was wondering if you knew how best to apply alpha to a section.

what i'm planning on doing is keeping a fixed number of sections where the first section would be nearly transparent and the most recent section would be fully opaque.  this would hopefully look like a trail following my object.

while i've got you on the line, would you also happen to know how i can apply a texture to these sections?  sorry, i'm a gameplay guy and i'm still learning the rendering side of things.

i appreciate any help you can provide!

Charles Humphrey wrote re: Light Cycles, Ribbons and Windows Phone 7
on Sat, Feb 12 2011 10:28 AM

You could apply an alpha value to the panel fading along the x coord based on it's time in existence, this would give you a nice fade.

To apply a texture, do it as you would texture any geom, if you have not done this before with custom shaders then maybe look at my HLSL tutorials.