public static void Hook06000036(Mesh mesh, Vector3[] vertices) { if (!bufmap.ContainsKey(vertices)) { if (!resmap.ContainsKey(mesh)) { resmap.Add(mesh, new List <Vector3[]>()); } VertexBuffer buf = null; foreach (Vector3[] v in resmap[mesh]) { if (v.Length != vertices.Length) { continue; } buf = bufmap[v]; for (int i = 0; i < v.Length; ++i) { if (v[i] != vertices[i]) { buf = null; break; } } if (buf != null) { break; } } if (buf == null) { VertexBufferDescription desc = mesh.VertexBuffer.Description; buf = new VertexBuffer(mesh.Device, mesh.VertexCount * 32, desc.Usage, VertexFormat.Position | VertexFormat.Texture1 | VertexFormat.Normal, desc.Pool); DataStream source = mesh.VertexBuffer.Lock(0, mesh.VertexCount * 32, LockFlags.ReadOnly); DataStream stream = buf.Lock(0, mesh.VertexCount * 32, LockFlags.None); for (int i = 0; i < vertices.Length; i++) { stream.Write(vertices[i]); source.Seek(12, SeekOrigin.Current); stream.WriteRange(source.ReadRange <float>(5)); } buf.Unlock(); mesh.VertexBuffer.Unlock(); } resmap[mesh].Add(vertices); bufmap.Add(vertices, buf); } mesh.Device.SetStreamSource(0, bufmap[vertices], 0, 32); mesh.Device.Indices = mesh.IndexBuffer; }
/// <summary> /// Creates a vertex buffer from IVertexData /// </summary> /// <param name="graphics">Graphics device</param> /// <param name="description">Vertex buffer description</param> /// <returns>Returns new buffer</returns> private static Buffer CreateVertexBuffer(Graphics graphics, VertexBufferDescription description) { string name = description.Name; var vertices = description.Data; bool dynamic = description.Dynamic; if (vertices?.Any() != true) { return(null); } var vertexType = vertices.First().VertexType; switch (vertexType) { case VertexTypes.Billboard: return(graphics.CreateVertexBuffer <VertexBillboard>(name, vertices, dynamic)); case VertexTypes.CPUParticle: return(graphics.CreateVertexBuffer <VertexCpuParticle>(name, vertices, dynamic)); case VertexTypes.GPUParticle: return(graphics.CreateVertexBuffer <VertexGpuParticle>(name, vertices, dynamic)); case VertexTypes.Terrain: return(graphics.CreateVertexBuffer <VertexTerrain>(name, vertices, dynamic)); case VertexTypes.Position: return(graphics.CreateVertexBuffer <VertexPosition>(name, vertices, dynamic)); case VertexTypes.PositionColor: return(graphics.CreateVertexBuffer <VertexPositionColor>(name, vertices, dynamic)); case VertexTypes.PositionTexture: return(graphics.CreateVertexBuffer <VertexPositionTexture>(name, vertices, dynamic)); case VertexTypes.PositionNormalColor: return(graphics.CreateVertexBuffer <VertexPositionNormalColor>(name, vertices, dynamic)); case VertexTypes.PositionNormalTexture: return(graphics.CreateVertexBuffer <VertexPositionNormalTexture>(name, vertices, dynamic)); case VertexTypes.PositionNormalTextureTangent: return(graphics.CreateVertexBuffer <VertexPositionNormalTextureTangent>(name, vertices, dynamic)); case VertexTypes.PositionSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPosition>(name, vertices, dynamic)); case VertexTypes.PositionColorSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPositionColor>(name, vertices, dynamic)); case VertexTypes.PositionTextureSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPositionTexture>(name, vertices, dynamic)); case VertexTypes.PositionNormalColorSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPositionNormalColor>(name, vertices, dynamic)); case VertexTypes.PositionNormalTextureSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPositionNormalTexture>(name, vertices, dynamic)); case VertexTypes.PositionNormalTextureTangentSkinned: return(graphics.CreateVertexBuffer <VertexSkinnedPositionNormalTextureTangent>(name, vertices, dynamic)); default: throw new EngineException(string.Format("Unknown vertex type: {0}", vertexType)); } }
public int MakeF3D(ROM rom, SortedRegionList vertexBytes, ScrollFactory factory) { int start = rom.offset; foreach (UInt64 cmd in header) { rom.Write64(cmd); rom.AddOffset(8); } // TODO: This is kinda stupid assumption but we believe there are more triangles in the beginning available... List <ScrollingTextureDescription> tds = map.Keys.ToList(); tds.Sort(new ScrollingTextureDescriptionComp()); foreach (ScrollingTextureDescription td in tds) { td.MakeF3D(rom); // Time to do optimization magic List <Triangle> textureTris = map[td]; // Create reverse map for vertices, vertices with more triangles will be used first Dictionary <Vertex, List <Triangle> > vertex2triMap = new Dictionary <Vertex, List <Triangle> >(); foreach (Triangle tri in textureTris) { for (int i = 0; i < 3; i++) { Vertex v = tri.vertices[i]; if (!vertex2triMap.TryGetValue(v, out List <Triangle> weight)) { vertex2triMap[v] = new List <Triangle>(); } vertex2triMap[v].Add(tri); } } // You quite literally can't eat more than that int vertexLength = 0x30 * textureTris.Count(); vertexBytes.CutContigRegion(vertexLength, out int vertexPosition); int allocVertexStart = vertexPosition; int allocVertexEnd = allocVertexStart + vertexLength; int writtenVerticesCount = 0; // Check if there are still vertices that needs to be worked with // Also we must make sure all lists in v2tm are not empty! while (vertex2triMap.Count != 0) { int segmentedVertexStart = rom.GetSegmentedAddress(vertexPosition); // vbd is main structure that holds information about used vertices space VertexBufferDescription vbd = new VertexBufferDescription((UInt32)segmentedVertexStart, 0xF); // Potential triangles also have weight depending on connection to other triangles and to added vertices // Weight +1 for each tri that shares vertice; +vertexPresentBonus for each vbd present vertex // It is recommended to have vertexPresentBonus be more than any weight that shared vertices can give // This way triangle that have the most weight will have the most vertices and potentially help others Dictionary <Triangle, int> potentialTris = new Dictionary <Triangle, int>(); // Add triangles to vbd till it won't be able to fit in the whole triangle while (vbd.freeCount != 0 && vertex2triMap.Count != 0) { // Right now no vertices are in vbd, pick one with the heighest weight and add it to vbd if (potentialTris.Count == 0) { // We need at least 3 vertices to draw not present tri if (vbd.freeCount < 3) { break; } KeyValuePair <Vertex, List <Triangle> > pair; // Let's ask for at least 5 vertices for optimal stuff, otherwise just put the worst triangle and call it if (vbd.freeCount < 5) { pair = vertex2triMap.Aggregate((prev, cur) => prev.Value.Count < cur.Value.Count ? prev : cur); } else { pair = vertex2triMap.Aggregate((prev, cur) => prev.Value.Count > cur.Value.Count ? prev : cur); } Vertex v = pair.Key; List <Triangle> tris = pair.Value; if (tris.Count == 0) { throw new ArithmeticException("Tris count in dict can't be 0"); } // Put current vertex and use found tris as potential next tri to add vbd.AddVertex(v); // Setup all potential triangles to could be added to vbd, the ones that have this vertex in common foreach (Triangle tri in tris) { potentialTris[tri] = 0; } // If vertex is in common between tris, give 1 each var tripairs = tris.Zip(tris.Skip(1), (a, b) => Tuple.Create(a, b)); foreach (Tuple <Triangle, Triangle> tripair in tripairs) { Triangle tri1 = tripair.Item1; Triangle tri2 = tripair.Item2; int common = tri1.CountCommon(tri2); potentialTris[tri1] += common; potentialTris[tri2] += common; } } // Find tri with biggest weight Triangle biggestTri = potentialTris.Aggregate((prev, cur) => prev.Value > cur.Value ? prev : cur).Key; // Add all triangles in vbd int[] indices = new int[3]; for (int i = 0; i < 3; i++) { Vertex v = biggestTri.vertices[i]; int index = vbd.AddVertex(v, out bool isNew); indices[i] = index; // Fix weights of tris if a new vertex was added to vbd (not already in vbd it is) if (isNew) { foreach (Triangle ptri in potentialTris.Keys.ToList()) { if (ptri.Contains(v)) { potentialTris[ptri] += vertexPresentBonus; } } } } // Draw the triangle, all vertices are guaranteed to be in vbd.DrawTriangle(indices); // As triangle was drawn, it needs to be removed from structs that had it // vertex2map & potentialTris // This way triangle won't be able to appear again foreach (Vertex v in biggestTri.vertices) { vertex2triMap[v].Remove(biggestTri); if (vertex2triMap[v].Count == 0) { vertex2triMap.Remove(v); } } potentialTris.Remove(biggestTri); // Setup new triangles to potentialTris HashSet <Triangle> newTris = new HashSet <Triangle>(); foreach (Vertex v in biggestTri.vertices) { if (vertex2triMap.TryGetValue(v, out List <Triangle> tris)) { foreach (Triangle tri in tris) { newTris.Add(tri); } } } // Also do not take into account tris that were added already // If we will take them into account, bad things will happen newTris.RemoveWhere(t => potentialTris.Keys.Contains(t)); // As new triangles appear, proceed to fix weights // First of all, initialize world: create new potential tris that will be merged later with potential tris // Initial value is -vertexPresentBonus as one vertex will be counted anyways Dictionary <Triangle, int> newPotentialTris = new Dictionary <Triangle, int>(); foreach (Triangle ntri in newTris) { newPotentialTris[ntri] = -vertexPresentBonus; } // Calculate weights for vertices that were present foreach (Triangle ntri in newTris) { foreach (Vertex v in vbd.vbuf) { if (ntri.Contains(v)) { newPotentialTris[ntri] += vertexPresentBonus; } } } // Check between triangles new/new var newNewTripairs = newTris.Zip(newTris.Skip(1), (a, b) => Tuple.Create(a, b)); foreach (Tuple <Triangle, Triangle> tripair in newNewTripairs) { Triangle tri1 = tripair.Item1; Triangle tri2 = tripair.Item2; int common = tri1.CountCommon(tri2); newPotentialTris[tri1] += common; newPotentialTris[tri2] += common; } // Check between triangles new/old List <Triangle> oldTris = potentialTris.Keys.ToList(); var newOldTripairs = newTris.Zip(oldTris, (a, b) => Tuple.Create(a, b)); foreach (Tuple <Triangle, Triangle> tripair in newOldTripairs) { Triangle newTri = tripair.Item1; Triangle oldTri = tripair.Item2; int common = newTri.CountCommon(oldTri); newPotentialTris[newTri] += common; potentialTris[oldTri] += common; } // Merge together old and new foreach (KeyValuePair <Triangle, int> pair in newPotentialTris) { potentialTris.Add(pair.Key, pair.Value); } // Check if there are triangles that have all vertexes inside the vbd // All such vertices could be initially drawn so just draw them // Triangles with weight over 2*vertexPresetBonus have 2+1 vertices in buffer so just draw them var drawnPairs = potentialTris.Where((t, w) => w >= 2 * vertexPresentBonus); foreach (KeyValuePair <Triangle, int> pair in drawnPairs) { Triangle ftri = pair.Key; for (int i = 0; i < 3; i++) { Vertex v = ftri.vertices[i]; int index = vbd.AddVertex(v, out bool isNew); indices[i] = index; // Fix weights of tris if a new vertex was added to vbd (not already in vbd it is) if (isNew) { throw new ArithmeticException("Triangles with high weight has new vertices!"); } } // Draw the triangle, all vertices are guaranteed to be in vbd.DrawTriangle(indices); potentialTris.Remove(biggestTri); } // Check if there are less then 2 vertices, which are required to be able to draw any tri // If there is a vertex that has vertexPresentBonus weight, we good, otherwise retreat if (vbd.freeCount == 1) { if (potentialTris.Values.ToList().FindIndex(w => w >= vertexPresentBonus) == -1) { break; } } } rom.PushOffset(vertexPosition); vbd.MakeData(rom); writtenVerticesCount += (rom.offset - vertexPosition) / 0x10; vertexPosition = rom.offset; rom.PopOffset(); vbd.MakeF3D(rom); } if (td.scroll != null) { // Vertices must be rounded by 3 because skelux scrolls work like that /* * if (writtenVerticesCount % 3 != 0) * { * int leftToRoundVertices = 3 - (writtenVerticesCount % 3); * writtenVerticesCount += leftToRoundVertices; * vertexPosition += leftToRoundVertices * 0x10; * } */ int segmentedAddress = rom.GetSegmentedAddress(allocVertexStart); List <ScrollObject> scrolls = factory.GetScrolls(writtenVerticesCount, segmentedAddress, td); foreach (ScrollObject scroll in scrolls) { scroll.WriteScroll(rom); } } vertexBytes.AddRegion(vertexPosition, allocVertexEnd - vertexPosition); } foreach (UInt64 cmd in footer) { rom.Write64(cmd); rom.AddOffset(8); } return(rom.offset - start); }