Game Development Community

T2dAnimationData

by David Everhart · in Torque X 2D · 12/05/2007 (7:19 am) · 7 replies

I have been through this class and realize it requires a material of type rendermaterial. This works great if the defaulteffect you are using for the material happens to be in the .txscene xml. However, what if I were to have a dynamic Image(PNG) created on the fly?

I tried this test code below:

System.Drawing.Image test = System.Drawing.Image.FromFile(@"data/images/SkelWalkNoSword.png");
            System.Drawing.Image test1 = System.Drawing.Image.FromFile(@"data/images/SwordWalk.png");
            System.Drawing.Image newImage = new Bitmap(test.Width, test.Height);
            System.Drawing.Graphics k = System.Drawing.Graphics.FromImage(newImage);
            k.DrawImage(test, new System.Drawing.Rectangle(0, 0, test.Width, test.Height));
            k.DrawImage(test1, new System.Drawing.Rectangle(0, 0, test.Width, test.Height));
            System.IO.MemoryStream imageStream = new MemoryStream(); ;
            newImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Png);
            Texture2D newTexture = Texture2D.FromFile(this.GraphicsDeviceManager.GraphicsDevice, imageStream);
//newImage.Save(@"c:\testimage.png");
            T2DAnimationData woot = new T2DAnimationData();
            DefaultEffect newEffect = new DefaultEffect();

newEffect.BindBackBufferTexture(newTexture);
            woot.CellCountX = 11;
            woot.CellCountY = 1;
            woot.CellHeight = 165;
            woot.CellWidth = 165;
            woot.AnimationDuration = .733333f;
            woot.AnimationCycle = true;
            woot.Material = newEffect;

The image is created fine, but it fails on the Texture2D fromfile, saying "This method is invalid". General google research suggested pixel shader and vertex shader issues, but I am using a GeForce 8800 GTS with 640 mbs, and the graphicdevicecapabilities class shows both at 3.0 and it only requires 1.1. Any ideas on what that issue is or a have a method to load streams into an T2DAnimationdata structure?

#1
12/05/2007 (10:35 am)
Bah, I forgot to reset my memorystream position:

imageStream.Position = 0;

That seems to load it, but the sprite is still empty. Time to debug!
#2
12/07/2007 (2:19 am)
David, I just tried this with the latest Torque X release 1.5.0 and it seems to work. Just change the DefaultEffect to SimpleMaterial... Starting with a new Torque X StarterGame 2D template, load the stock levelData.txscene file, select the GG logo, and set its name to ball in the Edit/Script rollout. Next, open up the Game.cs file and add your code to the BeginRun() method after the scene has been loaded. Next, add in your code (with a slight modification).

//read the source images
System.Drawing.Image test = System.Drawing.Image.FromFile(@"data/images/orange.png");
System.Drawing.Image test1 = System.Drawing.Image.FromFile(@"data/images/blue.png");
 
//create the new output image and render the composite
System.Drawing.Image newImage = new Bitmap(test.Width, test.Height);
System.Drawing.Graphics k = System.Drawing.Graphics.FromImage(newImage);
k.DrawImage(test, new System.Drawing.Rectangle(0, 0, test.Width, test.Height));
k.DrawImage(test1, new System.Drawing.Rectangle(0, 0, test.Width, test.Height));
 
//create the image output memory stream
System.IO.MemoryStream imageStream = new System.IO.MemoryStream(); ;
newImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Png);
imageStream.Position = 0; 
 
//reat the composite image stream into a new texture object
Texture2D newTexture = Texture2D.FromFile(this.GraphicsDeviceManager.GraphicsDevice, imageStream);
 
//create a new material and set it to the composite graphic
SimpleMaterial newEffect = new SimpleMaterial();
newEffect.SetTexture(newTexture);
 
//get a reference to the logo sprite in the scene and replace it with the new composite material
T2DStaticSprite ball = TorqueObjectDatabase.Instance.FindObject<T2DStaticSprite>("ball");
ball.Material = newEffect;

Source Images:
www.envygames.com/share/blue.png www.envygames.com/share/orange.png
Final Output:
www.envygames.com/share/output.jpg
For me, the orange and blue source .png files are triangles that face each other, to easily spot the overlap. As you can see from the image, your idea works great. I haven't tried dropping this into an animation, but it shouldn't be too much more work. In case you haven't try upgrading to the new Torque X release. Also, did you register the T2DAnimationData with the TorqueObjectDatabase? It wasn't in the code fragment above... keep in mind that nothing is rendered until it's registered.

John K.
#3
12/07/2007 (8:38 am)
Dang john, your the man! Ill incorporate this into my AnimatinCreator library I am building. This was the final peice that was eluding me, and rest assured, I will try it out tonight. The new library I am working on allows you to do:

int frameHeight = 165;
            int frameWidth = 165;
            // Set up our first frame definitions
          
            List<FrameDefinition> frameDefinitions = new List<FrameDefinition>();
            for (int i = 0; i < 11; i++)
            {
                //frameDefinitions.Add(new FrameDefinition(frameWidth * i, 0, frameHeight, frameWidth, i, attributes));
                frameDefinitions.Add(new FrameDefinition(frameWidth * i, 0, frameHeight, frameWidth, i));
            }
            // set up our images
            Image firstImage = Image.FromFile(@"data/images/SwordWalk.png");
            Image secondImage = Image.FromFile(@"data/images/SkelWalkNoSword.png");

            // Cache the new images
            AnimationDatabase.Instance.ImageSheets.Add("FirstImage",firstImage);
            AnimationDatabase.Instance.ImageSheets.Add("SecondImage",secondImage);

            //create our Framelayers
            FrameLayer firstFrameLayer = new FrameLayer();
            FrameLayer secondFrameLayer = new FrameLayer();

            // Load the FrameLayers, this will slice up the spritesheet into a List<Frame>
            firstFrameLayer.LoadSpriteSheet(firstImage, frameDefinitions);
            secondFrameLayer.LoadSpriteSheet(secondImage, frameDefinitions);

            // Cache our FrameLayers
            AnimationDatabase.Instance.FrameLayers.Add("FirstFrameLayer", firstFrameLayer);
            AnimationDatabase.Instance.FrameLayers.Add("SecondFrameLayer", secondFrameLayer);

            // Set their sort order
            firstFrameLayer.Definition.Sort = 0;
            secondFrameLayer.Definition.Sort = 1;


            //firstFrameLayer.FrameLayerImage.Save(@"c:\testFirstFrameLayerimage.png");
            //secondFrameLayer.FrameLayerImage.Save(@"c:\testSecondFrameLayerimage.png");
          
            // Create a new Animation
            Animation newAnimation = new Animation("TestAnimation");

            // Add our new animation layers
            newAnimation.FrameLayers.Add(firstFrameLayer);
            newAnimation.FrameLayers.Add(secondFrameLayer);

            // Merge them to create our new Animation
            Image finalImage = newAnimation.MergeFrameLayers();

            // Cache the new animation so it canbe retrieved later
            AnimationDatabase.Instance.Animations.Add(newAnimation.Definition.Name, newAnimation);

that code in my prototype (unoptimized) currently takes around 250 milliseconds, but once its cached:

FrameLayer firstFrameLayer = AnimationDatabase.Instance.FrameLayers["FirstFrameLayer"];
            FrameLayer secondFrameLayer = AnimationDatabase.Instance.FrameLayers["FirstFrameLayer"];
            // Create a new Animation
            Animation newAnimation = new Animation("TestAnimation1");

            // Add our new animation layers
            newAnimation.FrameLayers.Add(firstFrameLayer);
            newAnimation.FrameLayers.Add(secondFrameLayer);

            // Merge them to create our new Animation
            finalImage = newAnimation.MergeFrameLayers();

That one takes 31 milliseconds. I am thinking I could fire off a seperate thread at startup, loading the framelayers as needed, and animations. The benefit being that I can have say a helm framelayer (8 of them, one for each direction), chest framelayer, arms, right hand, left hand, etc and then at runtime, based on a configuration, generate a T2dAnimatedSprite. Thanks again John, you rock!
#4
12/07/2007 (9:11 am)
Ughh, after converting over my project to the new 1.5.0 to test this out, all my GUI is busted to hell and back :| Looks like they are using an xml approach versus the old classes. Yikes , squash one issue and another pops out!!
#5
12/07/2007 (10:30 am)
That's a very innovative approach! I'll need try this out. But I'm really surprised that 1.5.0 has broken your GUI code. By broken GUI, do you mean that the GarageGames.Torque.GUI classes (like GUISceneview) have changed, or do you mean that the levelData.txscene file is now filled with tags instead of tags? If its the ladder, then you can try running the ConvertBetaTXSCENE.exe tool located in the C:\Program Files\GarageGames\Torque X Pro\v1.5.0.0\Bin folder. Just keep in mind there's a bug and you need to then edit the levelData.txscene file manually to change to in order to get the level file to load again with Torque X Builder.

John K.
#6
12/07/2007 (3:22 pm)
Well, i was using the GUIControls from the torquecombatpro tutorial. Unfortunatley, those are not 1.5.0 compatible. As an example, they use spritefonts instead of fonts. I fixed a few, but after seeing a few more, just turned it off completely to test it out. I got it going all the way through, but its not showing yet. it is registered (I just didnt paste the rest of the code above). I am trying this:

// Get the Final Animation Image
            Image finalImage = MergeImages();
            
            //Create a new texture2D from it
            Texture2D newTexture = ImageHelper.ConvertImageToTexture2D(GFXDevice.Instance.Device, finalImage, ImageFormat.Png);

            // Create a new Simple Material
            SimpleMaterial newMaterial = new SimpleMaterial();
            newMaterial.SetTexture(newTexture);


            // Have to create a class based on texturedivider to tell it how to split up the texture, 
            //cellSizeDivider does not work since it requires its material to be set, but cellcountdivider works
            CellCountDivider newCellCountDivider = new CellCountDivider();
            newCellCountDivider.CellCountX = 11;
            newCellCountDivider.CellCountY = 1;
            // set the cellcountdivider to our new simple material
            newMaterial.TextureDivider = newCellCountDivider;

            // create and setup our animation data
            T2DAnimationData woot = new T2DAnimationData();
            woot.AnimationDuration = 0.733333f;
            woot.AnimationCycle = true;
            woot.Material = newMaterial;
            woot.AnimationFrames = "0 1 2 3 4 5 6 7 8 9 10";
            woot.Name = "woot";
            woot.Init();
            TorqueObjectDatabase.Instance.Register(woot);

            // setup our animation sprite
            T2DAnimatedSprite newSprite = new T2DAnimatedSprite(woot);
            newSprite.Position = new Microsoft.Xna.Framework.Vector2(100, 100);
            newSprite.Folder = TorqueObjectDatabase.Instance.CurrentFolder;
            newSprite.Visible = true;
            newSprite.Layer = 0;
            newSprite.Name = "test";
            // this is for test purposes
            newSprite.OnFrameChange =  delegate(int frame) { Console.WriteLine(frame.ToString());};
            TorqueObjectDatabase.Instance.Register(newSprite);
            //T2DSceneGraph testSceneGraph = (T2DSceneGraph)TorqueObjectDatabase.Instance.FindObject("DefaultSceneGraph");
            //testSceneGraph.AddObject(newSprite);
            newSprite.PlayAnimation();
#7
12/07/2007 (9:12 pm)
Ok, I forgot to set the size. Unfortunately, it wants itin world units, not pixels!! Time to look for a conversion routine, I dont know what scale the world units are in , just that there are 4 quadrants with x y coordinates.