/*public static void WriteToStreamFromRawDxt5a( * Stream dst, * byte[] src, * int srcOffset, * int width, * int height) { * var ew = new EndianBinaryWriter(dst, Endianness.LittleEndian); * * var imageSize = width * height / 2; * * ew.Write("DDS ", Encoding.ASCII, false); * ew.Write(124); * ew.Write(0x000A1007); * ew.Write(width); * ew.Write(height); * ew.Write(imageSize); * ew.Write(0); * ew.Write(1); * * for (var i = 0; i < 11; ++i) { * ew.Write(0); * } * * ew.Write(0x20); * ew.Write(4); * ew.Write("ATI1", Encoding.ASCII, false); * ew.Write(0); * ew.Write(0); * ew.Write(0); * ew.Write(0); * ew.Write(0); * ew.Write(0x401008); * * var fixedBuffer = new byte[imageSize]; * for (var i = 0; i < imageSize; i += 8) { * fixedBuffer[i + 0] = src[srcOffset + i + 1]; * fixedBuffer[i + 1] = src[srcOffset + i + 0]; * * fixedBuffer[i + 2] = src[srcOffset + i + 3]; * fixedBuffer[i + 3] = src[srcOffset + i + 2]; * fixedBuffer[i + 4] = src[srcOffset + i + 5]; * * fixedBuffer[i + 5] = src[srcOffset + i + 4]; * fixedBuffer[i + 6] = src[srcOffset + i + 7]; * fixedBuffer[i + 7] = src[srcOffset + i + 6]; * } * * ew.Position = 128; * ew.Write(fixedBuffer, 0, imageSize); * Asserts.Equal(128 + imageSize, ew.Position); * }*/ public static unsafe IImage DecompressDxt5a( byte[] src, int srcOffset, int width, int height) { const int blockSize = 4; var blockCountX = width / blockSize; var blockCountY = height / blockSize; var imageSize = width * height / 2; var monoTable = new byte[8]; var rIndices = new byte[16]; // TODO: Support grayscale? var bitmap = new I8Image(width, height); bitmap.Mutate((_, setHandler) => { for (var i = 0; i < imageSize; i += 8) { var iOff = srcOffset + i; // Gathers up color palette. var mono0 = monoTable[0] = src[iOff + 0]; var mono1 = monoTable[1] = src[iOff + 1]; var useEightIndexMode = mono0 > mono1; if (useEightIndexMode) { monoTable[2] = (byte)((6 * mono0 + 1 * mono1) / 7f); monoTable[3] = (byte)((5 * mono0 + 2 * mono1) / 7f); monoTable[4] = (byte)((4 * mono0 + 3 * mono1) / 7f); monoTable[5] = (byte)((3 * mono0 + 4 * mono1) / 7f); monoTable[6] = (byte)((2 * mono0 + 5 * mono1) / 7f); monoTable[7] = (byte)((1 * mono0 + 6 * mono1) / 7f); } else { monoTable[2] = (byte)((4 * mono0 + 1 * mono1) / 5f); monoTable[3] = (byte)((3 * mono0 + 2 * mono1) / 5f); monoTable[4] = (byte)((2 * mono0 + 3 * mono1) / 5f); monoTable[5] = (byte)((1 * mono0 + 4 * mono1) / 5f); monoTable[6] = 0; monoTable[7] = 255; } // Gathers up color indices. ulong indices = 0; for (var b = 0; b < 6; ++b) { ulong part = src[iOff + 2 + b]; part <<= (8 * b); indices |= part; } for (var ii = 0; ii < 16; ++ii) { rIndices[ii] = (byte)(indices & 7); indices >>= 3; } // Writes pixels to output image. // TODO: This might actually be flipped across the X/Y axis. This is // kept this way to align with the albedo texture for now. var tileIndex = i / 8; var tileY = tileIndex % blockCountY; var tileX = (tileIndex - tileY) / blockCountX; for (var j = 0; j < blockSize; j++) { for (var k = 0; k < blockSize; k++) { var value = monoTable[rIndices[(j * blockSize) + k]]; var outX = (tileX * blockSize) + j; var outY = tileY * blockSize + k; setHandler(outX, outY, value); } } } }); return(bitmap); }
public IModel LoadModel(OutModelFileBundle modelFileBundle) { var outFile = modelFileBundle.OutFile; var isBw2 = modelFileBundle.GameVersion == GameVersion.BW2; Stream stream; if (isBw2) { using var gZipStream = new GZipStream(outFile.Impl.OpenRead(), CompressionMode.Decompress); stream = new MemoryStream(); gZipStream.CopyTo(stream); stream.Position = 0; } else { stream = outFile.Impl.OpenRead(); } using var er = new EndianBinaryReader(stream, Endianness.LittleEndian); var bwHeightmap = isBw2 ? (IBwHeightmap)er.ReadNew <Bw2Heightmap>() : er.ReadNew <Bw1Heightmap>(); var finModel = new ModelImpl(); var finSkin = finModel.Skin; var finMesh = finSkin.AddMesh(); var triangles = new List <(IVertex, IVertex, IVertex)>(); var chunks = bwHeightmap.Chunks; var heightmapWidth = 64 * 4 * 4; var heightmapHeight = 64 * 4 * 4; var chunkFinVertices = new Grid <IVertex?>(heightmapWidth, heightmapHeight); var heights = new Grid <ushort>(heightmapWidth, heightmapHeight); for (var chunkY = 0; chunkY < chunks.Height; ++chunkY) { for (var chunkX = 0; chunkX < chunks.Width; ++chunkX) { var tiles = chunks[chunkX, chunkY]?.Tiles; if (tiles == null) { continue; } for (var tileY = 0; tileY < tiles.Height; ++tileY) { for (var tileX = 0; tileX < tiles.Width; ++tileX) { var points = tiles[tileX, tileY].Points; for (var pointY = 0; pointY < points.Height; ++pointY) { for (var pointX = 0; pointX < points.Width; ++pointX) { var point = points[pointX, pointY]; var heightmapX = 16 * chunkX + 4 * tileX + pointX; var heightmapY = 16 * chunkY + 4 * tileY + pointY; var finVertex = finSkin.AddVertex(point.X, point.Height, point.Y) .SetUv(1f * heightmapX / heightmapWidth, 1f * heightmapY / heightmapHeight); chunkFinVertices[heightmapX, heightmapY] = finVertex; heights[heightmapX, heightmapY] = point.Height; } } } } } } var image = new I8Image(heightmapWidth, heightmapHeight); image.Mutate((_, setHandler) => { for (var vY = 0; vY < heightmapHeight; ++vY) { for (var vX = 0; vX < heightmapWidth; ++vX) { setHandler(vX, vY, (byte)(heights[vX, vY] / 24)); } } }); var heightmapTexture = finModel.MaterialManager.CreateTexture(image); var finMaterial = finModel.MaterialManager.AddTextureMaterial(heightmapTexture); for (var vY = 0; vY < heightmapHeight - 1; ++vY) { for (var vX = 0; vX < heightmapWidth - 1; ++vX) { var a = chunkFinVertices[vX, vY]; var b = chunkFinVertices[vX + 1, vY]; var c = chunkFinVertices[vX, vY + 1]; var d = chunkFinVertices[vX + 1, vY + 1]; if (a != null && b != null && c != null && d != null) { triangles.Add((a, b, c)); triangles.Add((d, c, b)); } } } finMesh.AddTriangles(triangles.ToArray()).SetMaterial(finMaterial); return(finModel); }