/// <summary> /// Adds tangent and bitangent information to vertices. /// </summary> /// <param name="model">ModelData.</param> private void AddModelTangents(ref ModelData model) { foreach (var subMesh in model.SubMeshes) { int index = subMesh.StartIndex; for (int tri = 0; tri < subMesh.PrimitiveCount; tri++) { // Get indices short i1 = model.Indices[index++]; short i2 = model.Indices[index++]; short i3 = model.Indices[index++]; // Get vertices VertexPositionNormalTextureBump vert1 = model.Vertices[i1]; VertexPositionNormalTextureBump vert2 = model.Vertices[i2]; VertexPositionNormalTextureBump vert3 = model.Vertices[i3]; // Make tangent and bitangent MakeTangentBitangent(ref vert1, ref vert2, ref vert3); // Store updated vertices model.Vertices[i1] = vert1; model.Vertices[i2] = vert2; model.Vertices[i3] = vert3; } } }
/// <summary> /// Adds batch data to the batch builder. /// Geometry data is batched by key. /// </summary> /// <param name="key">Key to batch against.</param> /// <param name="batchData">Data to add.</param> /// <param name="matrix">Geometry transform to apply before adding.</param> public void AddToBuilder(uint key, BatchData batchData, Matrix matrix) { // Do nothing if sealed if (isSealed) { return; } BatchData builder; if (builderDictionary.ContainsKey(key)) { // Get current batch data builder = builderDictionary[key]; } else { // Start a new batch builder.Vertices = new List <VertexPositionNormalTextureBump>(); builder.Indices = new List <int>(); builderDictionary.Add(key, builder); } // Transform vertices for (int i = 0; i < batchData.Vertices.Count; i++) { VertexPositionNormalTextureBump vertex = batchData.Vertices[i]; vertex.Position = Vector3.Transform(vertex.Position, matrix); vertex.Normal = Vector3.TransformNormal(vertex.Normal, matrix); batchData.Vertices[i] = vertex; } // Add new vertices to builder int currentVertex = builder.Vertices.Count; builder.Vertices.AddRange(batchData.Vertices); // Update indices to new vertex base for (int i = 0; i < batchData.Indices.Count; i++) { batchData.Indices[i] += currentVertex; } // Add indices to builder builder.Indices.AddRange(batchData.Indices); // Update dictionary builderDictionary[key] = builder; }
/// <summary> /// Calculates tangent and bitangent values. /// Source1: Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html /// Source2: http://forums.create.msdn.com/forums/p/30443/172057.aspx /// </summary> private void MakeTangentBitangent( ref VertexPositionNormalTextureBump vert1, ref VertexPositionNormalTextureBump vert2, ref VertexPositionNormalTextureBump vert3) { Vector3 v1 = vert1.Position; Vector3 v2 = vert2.Position; Vector3 v3 = vert3.Position; Vector2 w1 = vert1.TextureCoordinate; Vector2 w2 = vert2.TextureCoordinate; Vector2 w3 = vert3.TextureCoordinate; // All points in a Daggerfall plane have the same vertex normal. // Each vertex normal is equal to the plane normal. Vector3 planeNormal = vert1.Normal; float x1 = v2.X - v1.X; float x2 = v3.X - v1.X; float y1 = v2.Y - v1.Y; float y2 = v3.Y - v1.Y; float z1 = v2.Z - v1.Z; float z2 = v3.Z - v1.Z; float s1 = w2.X - w1.X; float s2 = w3.X - w1.X; float t1 = w2.Y - w1.Y; float t2 = w3.Y - w1.Y; float r = 1.0f / (s1 * t2 - s2 * t1); Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); // Gram-Schmidt orthogonalize Vector3 tangent = sdir - planeNormal * Vector3.Dot(planeNormal, sdir); tangent.Normalize(); float tangentdir = (Vector3.Dot(Vector3.Cross(planeNormal, sdir), tdir) >= 0.0f) ? 1.0f : -1.0f; Vector3 binormal = Vector3.Cross(planeNormal, tangent) * tangentdir; vert1.Tangent = tangent; vert1.Binormal = binormal; vert2.Tangent = tangent; vert2.Binormal = binormal; vert3.Tangent = tangent; vert3.Binormal = binormal; }
/// <summary> /// Adds model data to the batch builder. /// </summary> /// <param name="key">Key to batch against.</param> /// <param name="modelData">Model data to add.</param> /// <param name="matrix">Transform to apply before adding model data.</param> /// <param name="matrix">Geometry transform to apply before adding.</param> public void AddToBuilder(ref ModelManager.ModelData modelData, Matrix matrix) { // Do nothing if sealed if (isSealed) { return; } // Iterate submeshes BatchData batchData; foreach (var sm in modelData.SubMeshes) { // Start new batch data for this submesh batchData.Vertices = new List <VertexPositionNormalTextureBump>(); batchData.Indices = new List <int>(); int counter = 0; int index = sm.StartIndex; for (int tri = 0; tri < sm.PrimitiveCount; tri++) { // Get indices int i1 = modelData.Indices[index++]; int i2 = modelData.Indices[index++]; int i3 = modelData.Indices[index++]; // Get vertices VertexPositionNormalTextureBump vert1 = modelData.Vertices[i1]; VertexPositionNormalTextureBump vert2 = modelData.Vertices[i2]; VertexPositionNormalTextureBump vert3 = modelData.Vertices[i3]; // Add vertices batchData.Vertices.Add(vert1); batchData.Vertices.Add(vert2); batchData.Vertices.Add(vert3); // Add indices batchData.Indices.Add(counter++); batchData.Indices.Add(counter++); batchData.Indices.Add(counter++); } // Add to builder AddToBuilder((uint)sm.MaterialKey, batchData, matrix); } }
/// <summary> /// Creates billboard template. /// </summary> private void CreateBillboard() { // Set dimensions of billboard const float w = 0.5f; const float h = 0.5f; // Create vertex array billboardVertices = new VertexPositionNormalTextureBump[4]; billboardVertices[0] = new VertexPositionNormalTextureBump( new Vector3(-w, h, 0), Vector3.Up, new Vector2(0, 0), Vector3.Zero, Vector3.Zero); billboardVertices[1] = new VertexPositionNormalTextureBump( new Vector3(w, h, 0), Vector3.Up, new Vector2(1, 0), Vector3.Zero, Vector3.Zero); billboardVertices[2] = new VertexPositionNormalTextureBump( new Vector3(-w, -h, 0), Vector3.Up, new Vector2(0, 1), Vector3.Zero, Vector3.Zero); billboardVertices[3] = new VertexPositionNormalTextureBump( new Vector3(w, -h, 0), Vector3.Up, new Vector2(1, 1), Vector3.Zero, Vector3.Zero); // Create index array billboardIndices = new int[6] { 0, 1, 2, 1, 3, 2, }; }
/// <summary> /// Create vertices for a terrain tile. /// </summary> /// <param name="dimension">Number of vertices wide and high.</param> /// <param name="scale">Size of each vertex in world units.</param> /// <remarks>Vertex array.</remarks> private VertexPositionNormalTextureBump[] CreateVertices(int dimension, float scale) { // Create vertex array int max = dimension + 1; VertexPositionNormalTextureBump[] vertices = new VertexPositionNormalTextureBump[max * max]; // Set vertices for (int x = 0; x < max; x++) { for (int y = 0; y < max; y++) { int pos = x + y * max; vertices[pos].Position = new Vector3(x * scale, 0f, y * scale); vertices[pos].Normal = Vector3.Up; vertices[pos].TextureCoordinate = new Vector2((float)x / (float)dimension, (float)y / (float)dimension); } } return(vertices); }
/// <summary> /// Adds exterior ground tiles to the batch. /// </summary> /// <param name="blockData">Block data.</param> private void AddRMBGroundTiles(ref DFBlock blockData) { // Make ground slightly lower to minimise depth-fighting on ground aligned polygons const float groundHeight = 0f; // Corner positions Vector3 topLeftPos, topRightPos, bottomLeftPos, bottomRightPos; Vector2 topLeftUV, topRightUV, bottomLeftUV, bottomRightUV; // Create vertices. These will be updated for each tile based on position and UV orientation. VertexPositionNormalTextureBump[] vertices = new VertexPositionNormalTextureBump[4]; // Create indices. These are the same for every tile. int[] indices = new int[] { 0, 1, 2, 1, 3, 2 }; // Loop through tiles int tileCount = 16; float tileDimension = 256.0f * ModelManager.GlobalScale; for (int y = 0; y < tileCount; y++) { for (int x = 0; x < tileCount; x++) { // Get source tile data DFBlock.RmbGroundTiles tile = blockData.RmbBlock.FldHeader.GroundData.GroundTiles[x, y]; // Set random terrain marker back to grass int textureRecord = (tile.TextureRecord > 55) ? 2 : tile.TextureRecord; // Create material BaseMaterialEffect material = core.ModelManager.CreateModelMaterial( (int)DFLocation.ClimateTextureSet.Exterior_Terrain, textureRecord); material.SamplerState0 = SamplerState.AnisotropicClamp; // Create vertices for this quad topLeftPos = new Vector3(x * tileDimension, groundHeight, y * tileDimension); topRightPos = new Vector3(topLeftPos.X + tileDimension, groundHeight, topLeftPos.Z); bottomLeftPos = new Vector3(topLeftPos.X, groundHeight, topLeftPos.Z + tileDimension); bottomRightPos = new Vector3(topLeftPos.X + tileDimension, groundHeight, topLeftPos.Z + tileDimension); // Set UVs if (tile.IsRotated && !tile.IsFlipped) { // Rotate only topLeftUV = new Vector2(1, 0); topRightUV = new Vector2(1, 1); bottomLeftUV = new Vector2(0, 0); bottomRightUV = new Vector2(0, 1); } else if (tile.IsFlipped && !tile.IsRotated) { // Flip only topLeftUV = new Vector2(1, 1); topRightUV = new Vector2(0, 1); bottomLeftUV = new Vector2(1, 0); bottomRightUV = new Vector2(0, 0); } else if (tile.IsRotated && tile.IsRotated) { // Rotate and flip topLeftUV = new Vector2(0, 1); topRightUV = new Vector2(0, 0); bottomLeftUV = new Vector2(1, 1); bottomRightUV = new Vector2(1, 0); } else { // No rotate or flip topLeftUV = new Vector2(0, 0); topRightUV = new Vector2(1, 0); bottomLeftUV = new Vector2(0, 1); bottomRightUV = new Vector2(1, 1); } // Set vertices vertices[0] = new VertexPositionNormalTextureBump(topLeftPos, Vector3.Up, topLeftUV, Vector3.Zero, Vector3.Zero); vertices[1] = new VertexPositionNormalTextureBump(topRightPos, Vector3.Up, topRightUV, Vector3.Zero, Vector3.Zero); vertices[2] = new VertexPositionNormalTextureBump(bottomLeftPos, Vector3.Up, bottomLeftUV, Vector3.Zero, Vector3.Zero); vertices[3] = new VertexPositionNormalTextureBump(bottomRightPos, Vector3.Up, bottomRightUV, Vector3.Zero, Vector3.Zero); // Add to builder staticGeometry.AddToBuilder(material.ID, vertices, indices, Matrix.Identity); } } }
/// <summary> /// Creates a new InstanceModel used by InstanceFactory to create new instances /// </summary> /// <param name="model">The Xna Model to be used</param> public InstanceModel(Model model) { // Temporary buffers for building the data. List <VertexPositionNormalTextureBump> vertices = new List <VertexPositionNormalTextureBump>(256); List <short> indices = new List <short>(256); Dictionary <int, int> indexremap = new Dictionary <int, int>(256); // Temporary buffers for extracting model data. VertexPositionNormalTextureBump[] sourcevertices = new VertexPositionNormalTextureBump[1]; short[] sourceindices = new short[1]; // Get the model transforms for baking down vertices into object space (XNA Models are stored in mesh space). Matrix[] bonearray = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(bonearray); for (int m = 0; m < model.Meshes.Count; m++) { ModelMesh mesh = model.Meshes[m]; // Test for correct vertex format. VerifyVertexFormat(mesh); // Get the mesh-to-object space transform. Matrix meshtoobject = bonearray[mesh.ParentBone.Index]; indexremap.Clear(); for (int p = 0; p < mesh.MeshParts.Count; p++) { ModelMeshPart part = mesh.MeshParts[p]; // Get the number of verts and indices (only VertexPositionNormalTextureBump and short are supported - these are used by SunBurn). int vertcount = part.VertexBuffer.VertexCount; int indcount = part.IndexBuffer.IndexCount; // Readjust the buffer sizes as necessary. if (sourcevertices.Length < vertcount) { sourcevertices = new VertexPositionNormalTextureBump[vertcount]; } if (sourceindices.Length < indcount) { sourceindices = new short[indcount]; } // Get the mesh data. part.VertexBuffer.GetData(sourcevertices, 0, vertcount); part.IndexBuffer.GetData(sourceindices, 0, indcount); // Loop through all of the vertices. for (int i = 0; i < (part.PrimitiveCount * 3); i++) { int index = sourceindices[i + part.StartIndex] + part.VertexOffset; // Did we already store the data in the vertex buffer? if (indexremap.ContainsKey(index)) { indices.Add((short)indexremap[index]); continue; } // Copy the vertex and convert to object space. VertexPositionNormalTextureBump vert = sourcevertices[index]; vert.Position = Vector3.Transform(vert.Position, meshtoobject); vert.Normal = Vector3.TransformNormal(vert.Normal, meshtoobject); vert.Tangent = Vector3.TransformNormal(vert.Tangent, meshtoobject); vert.Binormal = Vector3.TransformNormal(vert.Binormal, meshtoobject); vert.Normal.Normalize(); vert.Tangent.Normalize(); vert.Binormal.Normalize(); // Remap the source index (from the model) to the destination index (in the buffers). int destindex = vertices.Count; indexremap.Add(index, destindex); // Store the data. indices.Add((short)destindex); vertices.Add(vert); } } } // Convert the buffers to the final arrays. Vertices = vertices.ToArray(); Indices = indices.ToArray(); }
/// <summary> /// Creates a new ModelInstanceSourceData instance. /// </summary> public ModelInstanceSourceData(Model model) { // Temporary buffers for building the data. List<VertexPositionNormalTextureBump> vertices = new List<VertexPositionNormalTextureBump>(256); List<short> indices = new List<short>(256); Dictionary<int, int> indexremap = new Dictionary<int, int>(256); // Temporary buffers for extracting model data. VertexPositionNormalTextureBump[] sourcevertices = new VertexPositionNormalTextureBump[1]; short[] sourceindices = new short[1]; // Get the model transforms for baking down vertices into object space (XNA Models are stored in mesh space). Matrix[] bonearray = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(bonearray); for (int m = 0; m < model.Meshes.Count; m++) { ModelMesh mesh = model.Meshes[m]; // Test for correct vertex format. VerifyVertexFormat(mesh); // Get the number of verts and indices (only VertexPositionNormalTextureBump and short are supported - these are used by SunBurn). int vertcount = mesh.VertexBuffer.SizeInBytes / VertexPositionNormalTextureBump.SizeInBytes; int indcount = mesh.IndexBuffer.SizeInBytes / sizeof(short); // Readjust the buffer sizes as necessary. if (sourcevertices.Length < vertcount) sourcevertices = new VertexPositionNormalTextureBump[vertcount]; if (sourceindices.Length < indcount) sourceindices = new short[indcount]; // Get the mesh data. mesh.VertexBuffer.GetData<VertexPositionNormalTextureBump>(sourcevertices, 0, vertcount); mesh.IndexBuffer.GetData<short>(sourceindices, 0, indcount); // Get the mesh-to-object space transform. Matrix meshtoobject = bonearray[mesh.ParentBone.Index]; indexremap.Clear(); for (int p = 0; p < mesh.MeshParts.Count; p++) { ModelMeshPart part = mesh.MeshParts[p]; if (part.StreamOffset != 0) throw new Exception("Stream offset not supported."); // Loop through all of the vertices. for (int i = 0; i < (part.PrimitiveCount * 3); i++) { int index = sourceindices[i + part.StartIndex] + part.BaseVertex; // Did we already store the data in the vertex buffer? if (indexremap.ContainsKey(index)) { indices.Add((short)indexremap[index]); continue; } // Copy the vertex and convert to object space. VertexPositionNormalTextureBump vert = sourcevertices[index]; vert.Position = Vector3.Transform(vert.Position, meshtoobject); vert.Normal = Vector3.TransformNormal(vert.Normal, meshtoobject); vert.Tangent = Vector3.TransformNormal(vert.Tangent, meshtoobject); vert.Binormal = Vector3.TransformNormal(vert.Binormal, meshtoobject); vert.Normal.Normalize(); vert.Tangent.Normalize(); vert.Binormal.Normalize(); // Remap the source index (from the model) to the destination index (in the buffers). int destindex = vertices.Count; indexremap.Add(index, destindex); // Store the data. indices.Add((short)destindex); vertices.Add(vert); } } } // Convert the buffers to the final arrays. _Vertices = vertices.ToArray(); _Indices = indices.ToArray(); }
/// <summary> /// Creates static vertex and index buffers from builder. /// You can keep adding to the builder and calling this method to apply changes. /// </summary> public void ApplyBuilder() { // Do nothing if sealed if (isSealed) { return; } // Dispose current vertex buffer if (vertexBuffer != null) { vertexBuffer.Dispose(); vertexBuffer = null; } // Dispose current index buffer if (indexBuffer != null) { indexBuffer.Dispose(); indexBuffer = null; } // Dispose current batch dictionary if (batchDictionary != null) { batchDictionary.Clear(); batchDictionary = null; } // Create new batch dictionary batchDictionary = new Dictionary <uint, StaticBatch>(); // Count total vertices and indices int totalVertices = 0; int totalIndices = 0; foreach (var item in builderDictionary) { totalVertices += item.Value.Vertices.Count; totalIndices += item.Value.Indices.Count; } // Create static arrays VertexPositionNormalTextureBump[] allVertices = new VertexPositionNormalTextureBump[totalVertices]; int[] allIndices = new int[totalIndices]; // Populate static arrays int currentVertex = 0; int currentIndex = 0; foreach (var item in builderDictionary) { // Save current highest vertex and index int highestVertex = currentVertex; int highestIndex = currentIndex; // Copy vertex data foreach (var vertex in item.Value.Vertices) { allVertices[currentVertex++] = vertex; } // Copy index data foreach (var index in item.Value.Indices) { allIndices[currentIndex++] = highestVertex + index; } // Add batch details StaticBatch batch = new StaticBatch { StartIndex = highestIndex, PrimitiveCount = item.Value.Indices.Count / 3, }; batchDictionary.Add(item.Key, batch); } // Create new static buffers vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionNormalTextureBump.VertexDeclaration, allVertices.Length, BufferUsage.WriteOnly); indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.ThirtyTwoBits, allIndices.Length, BufferUsage.WriteOnly); // Set buffer data vertexBuffer.SetData <VertexPositionNormalTextureBump>(allVertices); indexBuffer.SetData <int>(allIndices); }