Processing Model -- directly process vertex position data by defining a custom TypeWriter and TypeReader

Source: Internet
Author: User
Problem

You want to access the positions of each vertex in the model. But you want an array of triangle objects. Each triangle object contains three Vector3 vectors, instead of placing all Vector3 in a large array as in the previous tutorial.

Or, in more general cases, you want to pass a custom content processor class object to an XNA program, but the content pipeline cannot know how to serialize or deserialize this class object, therefore, you must define your own TypeWriter and TypeReader.

Solution

The code of this tutorial is mainly based on the previous tutorial, but this time you create a simple triangle object for each of the three vertices, and then add all the generated triangle objects to a set, the set is stored in the Tag attribute of the model.

The main difference is that you will define a Triangle class in the Custom content pipeline. The default XNA content pipeline Cannot serialize your custom class into a file or form an object from a reverse string. So you define TypeWriter and TypeReader.

Working Principle

This tutorial can also be used to compile custom TypeWriter and TypeReader. See Figure 4-20. Locate the position of TypeWriter and TypeReader in the content pipeline.

Figure 4-20 custom processors, TypeWriter, and TypeReader in the content Pipeline

Go to Step 3-9 to create a custom content pipeline and add this class to your new content pipeline project:

public class Triangle {    private Vector3[] points;         public Triangle(Vector3 p0, Vector3 p1, Vector3 p2)     {        points = new Vector3[3];         points[0] = p0;         points[1] = p1;         points[2] = p2;     }        public Vector3[] Points { get { return points; } }             public Vector3 P0 { get { return points[0]; } }         public Vector3 P1 { get { return points[1]; } }         public Vector3 P2 { get { return points[2]; } } } 

This is a very simple class used to store three vector3. This class also provides the getter method to obtain a Vector3 or an array containing three Vector3 at a time.

For each triangle of the model, you want to create a custom Class Object, store these objects in an array, and store the array in the Tag attribute of the model, this can be used for real-time XNA code.

The Process class code is almost the same as the code in the 4-13 tutorial, but this time you save a set of Triangle objects instead of the Vector3 set in the previous tutorial. Add the Processor class after the Triangle class:

[ContentProcessor]public class ModelTriangleProcessor : ModelProcessor {    public override ModelContent Process(NodeContent input, ContentProcessorContextcontext)    {        ModelContent usualModel = base.Process(input, context);                 List<Triangle> triangles = new List<Triangle>();         triangles = AddVerticesToList(input, triangles);                 usualModel.Tag = triangles.ToArray();         return usualModel;     }} 

The AddVerticesToList method traverses the entire model structure, generates a Triangle object for each of the three Vector3, and adds all the Triangle objects to the set:

private List<Triangle> AddVerticesToList(NodeContent node, List<Triangle>triangleList) {    MeshContent mesh = node as MeshContent;         if (mesh != null)     {        Matrix absTransform = mesh.AbsoluteTransform;                 foreach (GeometryContent geo in mesh.Geometry)         {            //generate Triangle objects...         }    }        foreach (NodeContent child in node.Children)         triangleList = AddVerticesToList(child, triangleList);         return triangleList; } 

The above code is entirely from tutorial 4-13. The code for generating the Triangle object is as follows:

int triangles = geo.Indices.Count / 3; for (int currentTriangle = 0; currentTriangle < triangles; currentTriangle++) {    int index0 = geo.Indices[currentTriangle * 3 + 0];     int index1 = geo.Indices[currentTriangle * 3 + 1];     int index2 = geo.Indices[currentTriangle * 3 + 2];         Vector3 v0 = geo.Vertices.Positions[index0];     Vector3 v1 = geo.Vertices.Positions[index1];    Vector3 v2 = geo.Vertices.Positions[index2];         Vector3 transv0 = Vector3.Transform(v0, absTransform);     Vector3 transv1 = Vector3.Transform(v1, absTransform);    Vector3 transv2 = Vector3.Transform(v2, absTransform);         Triangle newTriangle = new Triangle(transv0, transv1, transv2);     triangleList.Add(newTriangle); } 

The above code is short, but simple code Looks harder.

The model is drawn based on the data in the index buffer, and each index corresponds to a vertex in the vertex buffer (see tutorial 5-3 to learn more about the index ). First, you need to know the total number of triangles in the model, which is equal to the number of indexes divided by 3.

Then, for each triangle, you first find the index. Each triangle is defined by three consecutive indexes. Therefore, you must first store these indexes in variables. Once you have these index values, you can obtain the corresponding vertex and transform these vertices through the absolute transformation matrix of the node (see tutorial 4-9 ), in this way, the vertex position can be changed to the initial position relative to the model, rather than the initial position relative to ModelMesh. Finally, you create a new Triangle object based on the three vertices and store the object in the set.

At the end of the Process method, this set is converted into an array of a Triangle object and stored in the Tag attribute of the model.

This is what we did at the end of the previous tutorial: you have stored the extra data in the Tag attribute of the model. However, when you import the model, if you select this Processor to process the model and try to compile the model, an error will occur:

Unsupported type. Cannot find a ContentTypeWriter implementation for ModelTriaPipeline.Triangle 
Write custom content TypeWriter

This error occurs only because the content pipeline does not know how to serialize a Triangle object into a binary file! Therefore, you must write a simple TypeWriter to process how the Triangle object is serialized. When a ModelContent object generated in the processor is processed to the Triangle object, XNA will call this TypeWriter to save the Triangle as a binary file.

Add this new class to the content pipeline project. This class needs to be placed outside the Processor class:

[ContentTypeWriter] public class TriangleTypeWriter : ContentTypeWriter<Triangle>{    protected override void Write(ContentWriter output, Triangle value)     {         output.WriteObject<Vector3>(value.P0);         output.WriteObject<Vector3>(value.P1);         output.WriteObject<Vector3>(value.P2);     }        public override string GetRuntimeReader(TargetPlatform targetPlatform)     {        return typeof(TriangleTypeReader).AssemblyQualifiedName;     }} 

The first two lines of code indicate that you will define a ContentTypeWriter that knows how to serialize a Triangle object.

First, you need to override the Write method, which takes every Triangle object you want to save as the parameter. This array is passed to the Write method as a value parameter. The Output variable contains a ContentWriter object that allows you to save it to a binary file.

When writing TypeWriter for a definite object, you must first consider what to store so that you can recreate these objects when loading binary files. Then, you need to break this object into more simple objects until the content pipeline knows how to serialize these simple objects.

For Triangle, storing three Vector3 allows you to recreate the Triangle. Fortunately, because the content pipeline knows how to serialize Vector3, the Write method simply splits the Triangle into three Vector3 and serializes it.

TIPS:You can traverse different overload methods of output, and the Write method can know which data type can be serialized by default content pipeline.

Note:You can also use output. Write (value. P0); this is because the Vector3 type is supported by default. However, the output. WriteObject method in the above Code is more common, because this method allows writing custom class objects in the Custom TypeWriter.

When XNA is started, binary files also need to be deserialized. The default content pipeline does not know how to deserialize the Triangle object, so you need to customize a TypeReader.

To allow the XNA program to find the corresponding custom TypeReader, your TypeWriter needs to specify the location of the TypeReader in the GetRuntimeReader method.

The GetRuntimeReader method (defined in the previous Code) simply returns a string indicating where the TypeReader for the Triangle object can be found.

If you run the code, an error occurs: ModelTriaPipeline. TriangleReader class cannot be found. This may be because you have not compiled this class. Therefore, add the last class to the ModelTriaPipeline namespace:

public class TriangleTypeReader : ContentTypeReader<Triangle>{    protected override Triangle Read(ContentReader input, Triangle existingInstance)     {        Vector3 p0 = input.ReadObject<Vector3>();         Vector3 p1 = input.ReadObject<Vector3>();         Vector3 p2 = input.ReadObject<Vector3>();                 Triangle newTriangle = new Triangle(p0, p1, p2);         return newTriangle;     }} 

You inherit from the ContentTypeReader class, so that your custom TypeReader can enumerate Triangle class objects.

Note:Make sure that the name of your custom reader is consistent with the name specified in the GetRuntimeReader method in writer! Otherwise, XNA still cannot find the corresponding TypeReader.

When the XNA project starts running, the Read method of this class is called every time a Triangle object is encountered in the binary file. You store three Vector3 in each triangle, so in TypeReader, you just need to read three Vector3 from the binary file, and then create a triangle object based on these three Vector3, returns a triangle.

Add reference in the content Pipeline Project

After compiling TypeReader, you are ready to run the program. However, although the project can be correctly generated, an error still occurs during the runtime:

Cannot find ContentTypeReader ModelTriaPipeline.Triangle, ModelTriaPipeline, Version=1.0.0.0, Culture=neutral 

This error occurs because the XNA project cannot access the ModelTriaPipeline namespace in TypeReader. To solve this problem, you need to add a reference in the content pipeline. Open the main XNA Project, select Project> Add Reference, and select the Projects tab in the pop-up dialog box. The content pipeline in the list is displayed, as shown in 4-21. Select and click OK.

Figure 4-21 Add a reference to a custom content Pipeline Project

Note:If your content pipeline is not in the list, make sure that you have generated the content pipeline project (see step 1 in Step 4-9 of tutorial ).

Here you need to add a reference to the Content pipeline in the Content directory of the XNA project and the XNA project itself, as shown in Figure 4-22. The first reference allows you to select a custom processor for the model, and the second reference allows the XNA project to call TypeReader in real time.

Figure 4-22 requires reference from two custom content Pipelines

After you select ModelTriangleProcessor to process the model and run the program, your model contains an array of the Triangle object in the Tag attribute.

Add a namespace for a content Pipeline

In the XNA project, when you want to access the Triangle object stored in the Tag attribute, you always convert the content in the Tag attribute to the type you want, in this example, It is Triangle [] (array of triangles ). However, because the Triangle class is defined in another namespace, you need to add its namespace before the Triangle Name:

ModelTriaPipeline.Triangle[] modelTriangles = (ModelTriaPipeline.Triangle[])myModel.Tag; 

This looks bad. If you use using to add the content pipeline namespace (ModelTriaPipeline in this example) to the XNA project:

using ModelTriaPipeline; 

All classes of the custom content pipeline can be known by the XNA project, which can shorten the Code:

Triangle[] modelTriangles = (Triangle[])myModel.Tag; 

Note:The location information stored in the Triangle is relative to the initial location of the model. See tutorial 4-14. How to use this location information when an animation is applied to a part of the model.

Step list for extending the processor using custom class objects

Here you can find the steps to run the content pipeline containing the custom class. As in tutorial 4-9, I will summarize a list for your reference. The initialization list in tutorial 4-9 is extended into two parts, which have been discussed earlier.

1. Add a content pipeline project in Solution

2. Add a reference to Microsoft. XNA. Framework. Content. Pipeline in the new project.

3. Add the Pipeline namespace in the using code block.

4. indicates the part you want to extend (this part of the method needs to be rewritten ).

5. Compile the new content pipeline project.

6. Add a new reference to the main project

7. select a new processor to process a clip.

8. Set Project dependencies.

9. Add a reference to the custom content pipeline in the XNA main program.

10. Add the content pipeline namespace to the using code block in the XNA main program.

Perform the following steps after initialization:

1. Define a custom class

2. Compile the code in step 1

3. Create a TypeWriter for the custom class used in each processor.

4. Create a TypeReader for the custom class used in each processor.

Note:The content importer, processor, and typewriters are invoke only when the project is compiled. Therefore, these classes cannot be deployed on the Xbox 360 platform, because the TypeReader class and your custom class cannot be found by the Game class. To solve this problem, you can move these two classes to the main XNA project and add references. You can see the sample code in this tutorial (the example in the XboxPipeline folder ).

Code

The complete code in the namespace of the custom content pipeline is as follows. This namespace includes a custom model processor with auxiliary methods, a custom class, And a TypeReader (storing the custom class as a binary file ), A TypeReader (custom Class Object for deserialization ).

namespace TrianglePipeline{    public class Triangle     {        private Vector3[] points;         public Triangle(Vector3 p0, Vector3 p1, Vector3 p2)         {            points = new Vector3[3];                         points[0] = p0;             points[1] = p1;             points[2] = p2;         }                public Vector3[] Points { get { return points; } }         public Vector3 P0 { get { return points[0]; } }         public Vector3 P1 { get { return points[1]; } }         public Vector3 P2 { get { return points[2]; } }     }        [ContentProcessor]     public class ModelTriangleProcessor : ModelProcessor     {        public override ModelContent Process(NodeContent input, ContentProcessorContext context)         {            ModelContent usualModel = base.Process(input, context);                         List<Triangle> triangles = new List<Triangle>();             triangles = AddVerticesToList(input, triangles);                         usualModel.Tag = triangles.ToArray();             return usualModel;         }                private List<Triangle> AddVerticesToList(NodeContent node, List<Triangle> triangleList)        {            MeshContent mesh = node as MeshContent;                         if (mesh != null)             {                Matrix absTransform = mesh.AbsoluteTransform;                 foreach (GeometryContent geo in mesh.Geometry)                 {                    int triangles = geo.Indices.Count / 3;                     for (int currentTriangle = 0; currentTriangle < triangles; currentTriangle++)                     {                         int index0 = geo.Indices[currentTriangle *3+ 0];                         int index1 = geo.Indices[currentTriangle *3+ 1];                         int index2 = geo.Indices[currentTriangle *3+ 2];                                                 Vector3 v0 = geo.Vertices.Positions[index0];                         Vector3 v1 = geo.Vertices.Positions[index1];                         Vector3 v2 = geo.Vertices.Positions[index2];                                                 Vector3 transv0 = Vector3.Transform(v0, absTransform);                         Vector3 transv1 = Vector3.Transform(v1, absTransform);                         Vector3 transv2 = Vector3.Transform(v2, absTransform);                                                 Triangle newTriangle = new Triangle(transv0, transv1, transv2);                         triangleList.Add(newTriangle);                     }                }            }                        foreach (NodeContent child in node.Children)                 triangleList = AddVerticesToList(child, triangleList);             return triangleList;         }    }        [ContentTypeWriter]     public class TriangleTypeWriter : ContentTypeWriter<Triangle>    {        protected override void Write(ContentWriter output, Triangle value)         {             output.WriteObject<Vector3>(value.P0);             output.WriteObject<Vector3>(value.P1);             output.WriteObject<Vector3>(value.P2);         }                public override string GetRuntimeReader(TargetPlatform targetPlatform)         {            return typeof(TriangleTypeReader).AssemblyQualifiedName;         }    }        public class TriangleTypeReader : ContentTypeReader<Triangle>    {        protected override Triangle Read(ContentReader input, Triangle existingInstance)         {            Vector3 p0 = input.ReadObject<Vector3>();             Vector3 p1 = input.ReadObject<Vector3>();             Vector3 p2 = input.ReadObject<Vector3>();                         Triangle newTriangle = new Triangle(p0, p1, p2);             return newTriangle;         }    }} 

In the main XNA project, load the model, access its Triangles, and place a breakpoint so that you can check the content of the modelTriangles array:

myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; Triangle[] modelTriangles = (Triangle[])myModel.Tag; System.Diagnostics.Debugger.Break(); 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.