mirror of
https://github.com/LazyDuchess/OpenTS2.git
synced 2025-01-22 08:11:47 -05:00
Merge pull request #24 from ammaraskar/lifo
Add support for deserializing Scenegraph LIFO blocks.
This commit is contained in:
commit
e07d862e6f
13 changed files with 168 additions and 37 deletions
|
@ -0,0 +1,9 @@
|
|||
namespace OpenTS2.Content.DBPF.Scenegraph
|
||||
{
|
||||
public class ScenegraphMipLevelInfoAsset : AbstractAsset
|
||||
{
|
||||
public byte[] MipData { get; }
|
||||
|
||||
public ScenegraphMipLevelInfoAsset(byte[] mipData) => (MipData) = (mipData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6b3b99c3961b425aa829124c461e827a
|
||||
timeCreated: 1676344211
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using OpenTS2.Common;
|
||||
using OpenTS2.Files.Formats.DBPF;
|
||||
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -16,8 +18,7 @@ namespace OpenTS2.Content.DBPF.Scenegraph
|
|||
|
||||
private Texture2D _texture;
|
||||
|
||||
// TODO: resolve LIFO references here.
|
||||
// Also, maybe we should just use `ContentProvider.Get` and compute this eagerly instead of having a
|
||||
// TODO: Maybe we should just use `ContentProvider.Get` and compute this eagerly instead of having a
|
||||
// ContentProvider passed in here. That way we could also drop having to store the full ImageDataBlock
|
||||
// and just have the more compact Texture2D.
|
||||
public Texture2D GetSelectedImageAsUnityTexture(ContentProvider provider)
|
||||
|
@ -28,44 +29,40 @@ namespace OpenTS2.Content.DBPF.Scenegraph
|
|||
}
|
||||
|
||||
var subImage = ImageDataBlock.SubImages[ImageDataBlock.SelectedImage];
|
||||
_texture = SubImageToTexture(ImageDataBlock.ColorFormat, ImageDataBlock.Width, ImageDataBlock.Height, subImage);
|
||||
_texture = SubImageToTexture(provider, ImageDataBlock.ColorFormat, ImageDataBlock.Width, ImageDataBlock.Height, subImage);
|
||||
return _texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the full Texture2D with mipmaps using the SubImage data.
|
||||
/// </summary>
|
||||
public static Texture2D SubImageToTexture(ScenegraphTextureFormat colorFormat, int width, int height, SubImage subImage)
|
||||
public static Texture2D SubImageToTexture(ContentProvider provider, ScenegraphTextureFormat colorFormat, int width, int height, SubImage subImage)
|
||||
{
|
||||
// Iterate backwards through the MipMap looking for the first non-LIFO mip.
|
||||
var highestReadableMipLevel = 0;
|
||||
for (int i = subImage.MipMap.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (subImage.MipMap[i] is LifoReferenceMip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
highestReadableMipLevel++;
|
||||
}
|
||||
|
||||
// Iterate up to the first non-LIFO mip.
|
||||
for (int i = 0; i < (subImage.MipMap.Length - highestReadableMipLevel); i++)
|
||||
{
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
}
|
||||
|
||||
var format = ScenegraphTextureFormatToUnity(colorFormat);
|
||||
var texture = new Texture2D(width, height, format, mipChain: true);
|
||||
|
||||
var currentMipLevel = 0;
|
||||
for (int i = highestReadableMipLevel - 1; i >= 0; i--)
|
||||
for (int i = subImage.MipMap.Length - 1; i >= 0; i--)
|
||||
{
|
||||
Debug.Assert(subImage.MipMap[i] is ByteArrayMip);
|
||||
var mip = subImage.MipMap[i] as ByteArrayMip;
|
||||
var mip = subImage.MipMap[i];
|
||||
byte[] mipData;
|
||||
switch (mip)
|
||||
{
|
||||
case LifoReferenceMip lifoReferenceMip:
|
||||
{
|
||||
var lifoAsset = provider.GetAsset<ScenegraphMipLevelInfoAsset>(
|
||||
new ResourceKey(lifoReferenceMip.LifoName, 0x1C0532FA, TypeIDs.SCENEGRAPH_LIFO));
|
||||
mipData = lifoAsset.MipData;
|
||||
break;
|
||||
}
|
||||
case ByteArrayMip byteArrayMip:
|
||||
mipData = byteArrayMip.Data;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"SubImage has invalid mip type: {mip}");
|
||||
}
|
||||
|
||||
var pixelData = ConvertPixelDataForUnity(colorFormat, mip.Data, width, height);
|
||||
var pixelData = ConvertPixelDataForUnity(colorFormat, mipData, width, height);
|
||||
texture.SetPixelData(pixelData, currentMipLevel);
|
||||
|
||||
currentMipLevel++;
|
||||
|
|
|
@ -653,6 +653,10 @@ namespace OpenTS2.Files.Formats.DBPF
|
|||
return Changes.ChangedEntries[entry.TGI].Change.Data.GetAsset();
|
||||
var item = GetBytes(entry, ignoreChanges);
|
||||
var codec = Codecs.Get(entry.GlobalTGI.TypeID);
|
||||
if (codec == null)
|
||||
{
|
||||
throw new ArgumentException($"No codec to handle type {entry.GlobalTGI.TypeID}");
|
||||
}
|
||||
var asset = codec.Deserialize(item, entry.GlobalTGI, this);
|
||||
asset.Compressed = InternalGetUncompressedSize(entry) > 0;
|
||||
asset.TGI = entry.TGI;
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace OpenTS2.Files.Formats.DBPF
|
|||
/// Texture Image in an RCOL wrapper.
|
||||
/// </summary>
|
||||
public const uint SCENEGRAPH_TXTR = Scenegraph.Block.ImageDataBlock.TYPE_ID;
|
||||
public const uint SCENEGRAPH_LIFO = Scenegraph.Block.MipLevelInfoBlock.TYPE_ID;
|
||||
|
||||
public const uint STR = 0x53545223;
|
||||
public const uint IMG = 0x856DDBAC;
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using OpenTS2.Files.Utils;
|
||||
|
||||
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
|
||||
{
|
||||
/// <summary>
|
||||
/// A Scenegraph block representing image data for a single mip level.
|
||||
/// </summary>
|
||||
public class MipLevelInfoBlock : ScenegraphDataBlock
|
||||
{
|
||||
public const uint TYPE_ID = 0xED534136;
|
||||
public const string BLOCK_NAME = "cLevelInfo";
|
||||
public override string BlockName => BLOCK_NAME;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes in a single row of the image.
|
||||
/// </summary>
|
||||
public uint Pitch { get; }
|
||||
|
||||
public byte[] Data { get; }
|
||||
|
||||
public MipLevelInfoBlock(PersistTypeInfo blockTypeInfo, int width, int height, uint pitch, byte[] data) : base(
|
||||
blockTypeInfo) =>
|
||||
(Width, Height, Pitch, Data) = (width, height, pitch, data);
|
||||
}
|
||||
|
||||
public class MipLevelInfoBlockReader : IScenegraphDataBlockReader<MipLevelInfoBlock>
|
||||
{
|
||||
public MipLevelInfoBlock Deserialize(IoBuffer reader, PersistTypeInfo blockTypeInfo)
|
||||
{
|
||||
var resource = ScenegraphResource.Deserialize(reader);
|
||||
|
||||
var width = reader.ReadInt32();
|
||||
var height = reader.ReadInt32();
|
||||
var pitch = reader.ReadUInt32();
|
||||
var data = reader.ReadBytes(reader.ReadUInt32());
|
||||
|
||||
return new MipLevelInfoBlock(blockTypeInfo, width, height, pitch, data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6a5ed1bd5cc744e7ac549e9937bd5f2b
|
||||
timeCreated: 1676137777
|
|
@ -0,0 +1,22 @@
|
|||
using System.IO;
|
||||
using OpenTS2.Common;
|
||||
using OpenTS2.Content;
|
||||
using OpenTS2.Content.DBPF.Scenegraph;
|
||||
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
|
||||
using OpenTS2.Files.Utils;
|
||||
|
||||
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Codecs
|
||||
{
|
||||
[Codec(TypeIDs.SCENEGRAPH_LIFO)]
|
||||
public class ScenegraphMipLevelInfoCodec : AbstractCodec
|
||||
{
|
||||
public override AbstractAsset Deserialize(byte[] bytes, ResourceKey tgi, DBPFFile sourceFile)
|
||||
{
|
||||
var stream = new MemoryStream(bytes);
|
||||
var reader = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
var mip = ScenegraphResourceCollection.DeserializeSingletonScenegraphBlock<MipLevelInfoBlock>(reader);
|
||||
return new ScenegraphMipLevelInfoAsset(mip.Data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8d1e7d1ed77427a9b6a8ae42a6fa0b9
|
||||
timeCreated: 1676345291
|
|
@ -93,6 +93,7 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph
|
|||
{
|
||||
{ TagExtensionBlock.TYPE_ID, new TagExtensionBlockReader() },
|
||||
{ ImageDataBlock.TYPE_ID, new ImageDataBlockReader() },
|
||||
{ MipLevelInfoBlock.TYPE_ID, new MipLevelInfoBlockReader() },
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using OpenTS2.Content;
|
||||
using OpenTS2.Content.DBPF.Scenegraph;
|
||||
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
|
||||
using UnityEngine;
|
||||
|
@ -22,7 +23,8 @@ public class ScenegraphTextureAssetTest
|
|||
var mip = new ImageMip[] { new ByteArrayMip(imageData) };
|
||||
var subImage = new SubImage(mip, Color.white, 1.0f);
|
||||
|
||||
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ScenegraphTextureFormat.DXT3, 128, 128, subImage);
|
||||
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ContentProvider.Get(), ScenegraphTextureFormat.DXT3,
|
||||
128, 128, subImage);
|
||||
|
||||
var actualImage = new Texture2D(128, 128, TextureFormat.RGBA32, mipChain:false);
|
||||
actualImage.LoadImage(File.ReadAllBytes("TestAssets/Scenegraph/brick-texture.png"));
|
||||
|
@ -43,7 +45,8 @@ public class ScenegraphTextureAssetTest
|
|||
var mip = new ImageMip[] { new ByteArrayMip(imageData) };
|
||||
var subImage = new SubImage(mip, Color.white, 1.0f);
|
||||
|
||||
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ScenegraphTextureFormat.DXT3, 256, 128, subImage);
|
||||
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ContentProvider.Get(), ScenegraphTextureFormat.DXT3,
|
||||
256, 128, subImage);
|
||||
|
||||
var actualImage = new Texture2D(128, 128, TextureFormat.RGBA32, mipChain:false);
|
||||
actualImage.LoadImage(File.ReadAllBytes("TestAssets/Scenegraph/cc0-logo.png"));
|
||||
|
|
|
@ -4,6 +4,7 @@ using NUnit.Framework;
|
|||
using OpenTS2.Common;
|
||||
using OpenTS2.Content;
|
||||
using OpenTS2.Content.DBPF.Scenegraph;
|
||||
using OpenTS2.Files.Formats.DBPF;
|
||||
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -17,29 +18,70 @@ public class ScenegraphTextureCodecTest
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoadsTexture()
|
||||
public void TestLoadsTexturesWithNoLifo()
|
||||
{
|
||||
var textureAsset =
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
|
||||
0x1C4A276C));
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_no_lifo_txtr", 0x1C0532FA,
|
||||
TypeIDs.SCENEGRAPH_TXTR));
|
||||
var texture = textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get());
|
||||
|
||||
Assert.That(texture.width, Is.EqualTo(128));
|
||||
Assert.That(texture.height, Is.EqualTo(128));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestLoadedImageBlockHasCorrectDetails()
|
||||
public void TestLoadedImageBlockWithNoLifoHasCorrectDetails()
|
||||
{
|
||||
var textureAsset =
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
|
||||
0x1C4A276C));
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_no_lifo_txtr", 0x1C0532FA,
|
||||
TypeIDs.SCENEGRAPH_TXTR));
|
||||
var imageBlock = textureAsset.ImageDataBlock;
|
||||
|
||||
Assert.That(imageBlock.BlockName, Is.EqualTo("cImageData"));
|
||||
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
|
||||
Assert.That(imageBlock.Width, Is.EqualTo(128));
|
||||
Assert.That(imageBlock.Height, Is.EqualTo(128));
|
||||
|
||||
Assert.That(imageBlock.SubImages.Length, Is.EqualTo(1));
|
||||
Assert.That(imageBlock.SelectedImage, Is.EqualTo(0));
|
||||
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
|
||||
|
||||
Assert.That(imageBlock.SubImages[0].MipMap.Length, Is.EqualTo(8));
|
||||
Assert.That(imageBlock.SubImages[0].MipMap[0], Is.InstanceOf<ByteArrayMip>());
|
||||
Assert.That(imageBlock.SubImages[0].MipMap[7], Is.InstanceOf<ByteArrayMip>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoadsTextureWithLifo()
|
||||
{
|
||||
var textureAsset =
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
|
||||
TypeIDs.SCENEGRAPH_TXTR));
|
||||
var texture = textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get());
|
||||
|
||||
Assert.That(texture.width, Is.EqualTo(256));
|
||||
Assert.That(texture.height, Is.EqualTo(256));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoadedImageBlockWithLifoHasCorrectDetails()
|
||||
{
|
||||
var textureAsset =
|
||||
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
|
||||
TypeIDs.SCENEGRAPH_TXTR));
|
||||
var imageBlock = textureAsset.ImageDataBlock;
|
||||
|
||||
Assert.That(imageBlock.BlockName, Is.EqualTo("cImageData"));
|
||||
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
|
||||
Assert.That(imageBlock.Width, Is.EqualTo(256));
|
||||
Assert.That(imageBlock.Height, Is.EqualTo(256));
|
||||
|
||||
Assert.That(imageBlock.SubImages.Length, Is.EqualTo(1));
|
||||
Assert.That(imageBlock.SelectedImage, Is.EqualTo(0));
|
||||
|
||||
Assert.That(imageBlock.SubImages[0].MipMap.Length, Is.EqualTo(9));
|
||||
// Check that the first mip just contains image data.
|
||||
Assert.That(imageBlock.SubImages[0].MipMap[0], Is.InstanceOf<ByteArrayMip>());
|
||||
// Check that the last mip is a LIFO reference.
|
||||
Assert.That(imageBlock.SubImages[0].MipMap[8], Is.InstanceOf<LifoReferenceMip>());
|
||||
}
|
||||
}
|
Binary file not shown.
Loading…
Reference in a new issue