Kursach_StealthAction/Assets/Tremble/IO/StreamIO.cs
2026-10-11 19:16:11 +03:00

246 lines
5.7 KiB
C#

//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
// -----------------------------------------------------------------------------------------------------------------------------
// Writing
// -----------------------------------------------------------------------------------------------------------------------------
// For writing to the MD3 stream, we bypass BinaryWriter because it's MUCH slower.
// I think it's because the BinaryWriter does Endianness and Encoding related things, which we
// don't need for our limited use-case of ASCII-encoded MD3 models.
public class StreamWriteIO : IDisposable
{
private const int BUFFER_SIZE_KB = 160; // 160kB buffer
public StreamWriteIO(Stream stream)
{
m_Stream = stream;
}
public void Dispose()
{
Flush();
}
private readonly Stream m_Stream;
private readonly byte[] m_Buffer = new byte[BUFFER_SIZE_KB * 1024];
private int m_BufPtr;
private readonly float[] m_Floats = new float[1];
private readonly byte[] m_Bytes = new byte[4];
public void WriteInt32(int value)
{
WriteByte((byte)(value & 0xFF));
WriteByte((byte)((value >> 8) & 0xFF));
WriteByte((byte)((value >> 16) & 0xFF));
WriteByte((byte)((value >> 24) & 0xFF));
}
public void WriteFloat(float value)
{
// Block copy the float value's bytes into preallocated buffer
m_Floats[0] = value;
Buffer.BlockCopy(m_Floats, 0, m_Bytes, 0, 4);
// Write out
WriteByte(m_Bytes[0]);
WriteByte(m_Bytes[1]);
WriteByte(m_Bytes[2]);
WriteByte(m_Bytes[3]);
}
public void WriteInt16(short value)
{
WriteByte((byte)(value & 0xFF));
WriteByte((byte)((value >> 8) & 0xFF));
}
public void WriteNormalAsLatLong(Vector3 normal)
{
const float BYTE_TO_DEG = (255.0f / 360.0f) * Mathf.Rad2Deg;
Vector3 q3Normal = normal.UnityToQ3Vector(scale: 1f);
if (q3Normal is { x: 0, y: 0 })
{
WriteByte(q3Normal.z > 0 ? (byte)0x00 : (byte)0x80); // U
WriteByte(0x00); // V
}
else
{
WriteByte((byte)(Mathf.Acos(q3Normal.z) * BYTE_TO_DEG)); // U
WriteByte((byte)(Mathf.Atan2(q3Normal.y, q3Normal.x) * BYTE_TO_DEG)); // V
}
}
public void WriteBytes(byte[] bytes)
{
foreach (byte b in bytes)
{
WriteByte(b);
}
}
public void WriteBytes(byte[] buffer, int count)
{
for (int b = 0; b < count; b++)
{
WriteByte(buffer[b]);
}
}
public void WriteString(string value)
{
foreach (char c in value)
{
WriteByte((byte)c);
}
}
public void WriteString(string value, int length)
{
for (int i = 0; i < length; i++)
{
if (i < value.Length)
{
WriteByte((byte)value[i]);
}
else
{
WriteByte(0x00);
}
}
}
private void WriteByte(byte b)
{
if (m_BufPtr >= m_Buffer.Length)
{
Flush();
}
m_Buffer[m_BufPtr++] = b;
}
private void Flush()
{
if (m_BufPtr == 0)
return;
m_Stream.Write(m_Buffer, 0, m_BufPtr);
m_BufPtr = 0;
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// Reading
// -----------------------------------------------------------------------------------------------------------------------------
public class StreamReadIO
{
private readonly float[] m_Floats = new float[1];
private readonly byte[] m_Bytes = new byte[8];
private readonly byte[] m_StreamContents;
private int m_Offset;
public StreamReadIO(string path)
{
m_StreamContents = File.ReadAllBytes(path);
}
public void Seek(int offset)
{
m_Offset = offset;
}
public int Position => m_Offset;
public byte ReadByte()
{
return m_StreamContents[m_Offset++];
}
public void SkipBytes(int count)
{
m_Offset += count;
}
public short ReadInt16()
{
m_Bytes[0] = ReadByte();
m_Bytes[1] = ReadByte();
return (short)(m_Bytes[0] | m_Bytes[1] << 8);
}
public int ReadInt32()
{
m_Bytes[0] = ReadByte();
m_Bytes[1] = ReadByte();
m_Bytes[2] = ReadByte();
m_Bytes[3] = ReadByte();
return (int)(m_Bytes[0] | m_Bytes[1] << 8 | m_Bytes[2] << 16 | m_Bytes[3] << 24);
}
public float ReadSingle()
{
// Block copy the bytes into a float, using a preallocated buffer
m_Bytes[0] = ReadByte();
m_Bytes[1] = ReadByte();
m_Bytes[2] = ReadByte();
m_Bytes[3] = ReadByte();
Buffer.BlockCopy(m_Bytes, 0, m_Floats, 0, 4);
// Return
return m_Floats[0];
}
public char[] ReadChars(int numChars)
{
char[] result = new char[numChars];
for (int i = 0; i < numChars; i++)
{
result[i] = (char)ReadByte();
}
return result;
}
public byte[] ReadBytes(int numBytes)
{
byte[] result = new byte[numBytes];
Buffer.BlockCopy(m_StreamContents, m_Offset, result, 0, numBytes);
m_Offset += numBytes;
return result;
}
public string ReadString(int numChars)
{
string result = new(ReadChars(numChars));
int nulCharacter = result.IndexOf('\0');
return nulCharacter > 0 ? result.Substring(0, nulCharacter) : result;
}
public Vector3 ReadVector3(bool convertToQ3 = false, float scale = 1f)
{
Vector3 result = new(ReadSingle() * scale, ReadSingle() * scale, ReadSingle() * scale);
return convertToQ3 ? result.Q3ToUnityVector() : result;
}
public Vector2 ReadUV()
=> new(ReadSingle() + 0.5f, 1f - ReadSingle() + 0.5f); // Flip Y and offset by 0.5, 0.5
public Color32 ReadColour()
=> new(ReadByte(), ReadByte(), ReadByte(), ReadByte());
public Vector3Int ReadVector3Int() => new(ReadInt32(), ReadInt32(), ReadInt32());
public Vector2Int ReadVector2Int() => new(ReadInt32(), ReadInt32());
}
}