2021-04-17 22:30:32 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using TC = System.ComponentModel.TypeConverterAttribute;
|
|
|
|
|
using EXP = System.ComponentModel.ExpandableObjectConverter;
|
|
|
|
|
using SharpDX;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Xml;
|
|
|
|
|
|
|
|
|
|
namespace CodeWalker.GameFiles
|
|
|
|
|
{
|
|
|
|
|
[TC(typeof(EXP))]
|
|
|
|
|
public class HeightmapFile : GameFile, PackedFile
|
|
|
|
|
{
|
|
|
|
|
public byte[] RawFileData { get; set; }
|
2021-04-18 18:39:49 +08:00
|
|
|
|
public Endianess Endianess { get; set; } = Endianess.BigEndian;
|
2021-04-17 22:30:32 +08:00
|
|
|
|
|
|
|
|
|
public uint Magic { get; set; } = 0x484D4150; //'HMAP'
|
|
|
|
|
public byte VersionMajor { get; set; } = 1;
|
|
|
|
|
public byte VersionMinor { get; set; } = 1;
|
|
|
|
|
public ushort Pad { get; set; }
|
|
|
|
|
public uint Compressed { get; set; } = 1;
|
|
|
|
|
public ushort Width { get; set; }
|
|
|
|
|
public ushort Height { get; set; }
|
|
|
|
|
public Vector3 BBMin { get; set; }
|
|
|
|
|
public Vector3 BBMax { get; set; }
|
|
|
|
|
public uint Length { get; set; }
|
|
|
|
|
public CompHeader[] CompHeaders { get; set; }
|
|
|
|
|
public byte[] MaxHeights { get; set; }
|
|
|
|
|
public byte[] MinHeights { get; set; }
|
|
|
|
|
|
|
|
|
|
public HeightmapFile() : base(null, GameFileType.Heightmap)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
public HeightmapFile(RpfFileEntry entry) : base(entry, GameFileType.Heightmap)
|
|
|
|
|
{
|
|
|
|
|
RpfFileEntry = entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Load(byte[] data, RpfFileEntry entry)
|
|
|
|
|
{
|
|
|
|
|
RawFileData = data;
|
|
|
|
|
if (entry != null)
|
|
|
|
|
{
|
|
|
|
|
RpfFileEntry = entry;
|
|
|
|
|
Name = entry.Name;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-18 18:39:49 +08:00
|
|
|
|
if (BitConverter.ToUInt32(data, 0) == Magic)
|
|
|
|
|
{
|
|
|
|
|
Endianess = Endianess.LittleEndian;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-17 22:30:32 +08:00
|
|
|
|
using (MemoryStream ms = new MemoryStream(data))
|
|
|
|
|
{
|
2021-04-18 18:39:49 +08:00
|
|
|
|
DataReader r = new DataReader(ms, Endianess);
|
2021-04-17 22:30:32 +08:00
|
|
|
|
|
|
|
|
|
Read(r);
|
|
|
|
|
}
|
2021-04-18 18:39:49 +08:00
|
|
|
|
|
|
|
|
|
//var pgm = GetPGM();
|
|
|
|
|
|
2021-04-17 22:30:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] Save()
|
|
|
|
|
{
|
|
|
|
|
MemoryStream s = new MemoryStream();
|
2021-04-18 18:39:49 +08:00
|
|
|
|
DataWriter w = new DataWriter(s, Endianess);
|
2021-04-17 22:30:32 +08:00
|
|
|
|
|
|
|
|
|
Write(w);
|
|
|
|
|
|
|
|
|
|
var buf = new byte[s.Length];
|
|
|
|
|
s.Position = 0;
|
|
|
|
|
s.Read(buf, 0, buf.Length);
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Read(DataReader r)
|
|
|
|
|
{
|
|
|
|
|
Magic = r.ReadUInt32();
|
|
|
|
|
VersionMajor = r.ReadByte();
|
|
|
|
|
VersionMinor = r.ReadByte();
|
|
|
|
|
Pad = r.ReadUInt16();
|
|
|
|
|
Compressed = r.ReadUInt32();
|
|
|
|
|
Width = r.ReadUInt16();
|
|
|
|
|
Height = r.ReadUInt16();
|
|
|
|
|
BBMin = r.ReadVector3();
|
|
|
|
|
BBMax = r.ReadVector3();
|
|
|
|
|
Length = r.ReadUInt32();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Length != (r.Length - r.Position))
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var dlen = (int)Length;
|
|
|
|
|
if (Compressed > 0)
|
|
|
|
|
{
|
|
|
|
|
CompHeaders = new CompHeader[Height];
|
|
|
|
|
for (int i = 0; i < Height; i++)
|
|
|
|
|
{
|
|
|
|
|
CompHeaders[i].Read(r);
|
|
|
|
|
}
|
|
|
|
|
dlen -= (Height * 8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((r.Length - r.Position) != dlen)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
var d = r.ReadBytes(dlen);
|
|
|
|
|
|
|
|
|
|
if ((r.Length - r.Position) != 0)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
if (Compressed > 0)
|
|
|
|
|
{
|
|
|
|
|
MaxHeights = new byte[Width * Height];
|
|
|
|
|
MinHeights = new byte[Width * Height];
|
|
|
|
|
var h2off = dlen / 2; //is this right?
|
|
|
|
|
for (int y = 0; y < Height; y++)
|
|
|
|
|
{
|
|
|
|
|
var h = CompHeaders[y];
|
|
|
|
|
for (int i = 0; i < h.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
int x = h.Start + i;
|
|
|
|
|
int o = h.DataOffset + x;
|
|
|
|
|
MaxHeights[y * Width + x] = d[o];
|
|
|
|
|
MinHeights[y * Width + x] = d[o + h2off];
|
|
|
|
|
}
|
|
|
|
|
for (int x = 0; x < Width; x++)
|
|
|
|
|
{
|
|
|
|
|
var hm1v = MaxHeights[y * Width + x];
|
|
|
|
|
var hm2v = MinHeights[y * Width + x];
|
|
|
|
|
var diff = hm1v - hm2v;
|
|
|
|
|
if ((diff <= 0) && (hm1v != 0))
|
|
|
|
|
{ }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MaxHeights = d; //no way to test this as vanilla heightmaps are compressed...
|
|
|
|
|
MinHeights = d; //this won't work anyway.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
private void Write(DataWriter w)
|
|
|
|
|
{
|
|
|
|
|
var d = MaxHeights;
|
|
|
|
|
if (Compressed > 0)
|
|
|
|
|
{
|
|
|
|
|
var ch = new CompHeader[Height];
|
|
|
|
|
var d1 = new List<byte>();
|
|
|
|
|
var d2 = new List<byte>();
|
|
|
|
|
for (int y = 0; y < Height; y++)
|
|
|
|
|
{
|
|
|
|
|
var start = 0;
|
|
|
|
|
var end = 0;
|
|
|
|
|
for (int x = 0; x < Width; x++)
|
|
|
|
|
{
|
|
|
|
|
if (MaxHeights[y * Width + x] != 0) { start = x; break; }
|
|
|
|
|
}
|
|
|
|
|
for (int x = Width - 1; x >= 0; x--)
|
|
|
|
|
{
|
|
|
|
|
if (MaxHeights[y * Width + x] != 0) { end = x + 1; break; }
|
|
|
|
|
}
|
|
|
|
|
var count = end - start;
|
|
|
|
|
var offset = (count > 0) ? d1.Count - start : 0;
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
var x = start + i;
|
|
|
|
|
var n = y * Width + x;
|
|
|
|
|
d1.Add(MaxHeights[n]);
|
|
|
|
|
d2.Add(MinHeights[n]);
|
|
|
|
|
}
|
|
|
|
|
var h = new CompHeader() { Start = (ushort)start, Count = (ushort)count, DataOffset = offset };
|
|
|
|
|
ch[y] = h;
|
|
|
|
|
}
|
|
|
|
|
d1.AddRange(d2);//the 2 sets of compressed data are just smushed together
|
|
|
|
|
d = d1.ToArray();
|
|
|
|
|
CompHeaders = ch;
|
|
|
|
|
Length = (uint)(d.Length + Height * 8);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Length = (uint)d.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w.Write(Magic);
|
|
|
|
|
w.Write(VersionMajor);
|
|
|
|
|
w.Write(VersionMinor);
|
|
|
|
|
w.Write(Pad);
|
|
|
|
|
w.Write(Compressed);
|
|
|
|
|
w.Write(Width);
|
|
|
|
|
w.Write(Height);
|
|
|
|
|
w.Write(BBMin);
|
|
|
|
|
w.Write(BBMax);
|
|
|
|
|
w.Write(Length);
|
|
|
|
|
if (Compressed > 0)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < Height; i++)
|
|
|
|
|
{
|
|
|
|
|
CompHeaders[i].Write(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w.Write(d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
|
|
|
{
|
2021-04-18 18:39:49 +08:00
|
|
|
|
if (Endianess != Endianess.BigEndian)
|
|
|
|
|
{
|
|
|
|
|
HmapXml.StringTag(sb, indent, "Endianess", Endianess.ToString());
|
|
|
|
|
}
|
2021-04-17 22:30:32 +08:00
|
|
|
|
HmapXml.ValueTag(sb, indent, "Width", Width.ToString());
|
|
|
|
|
HmapXml.ValueTag(sb, indent, "Height", Height.ToString());
|
|
|
|
|
HmapXml.SelfClosingTag(sb, indent, "BBMin " + FloatUtil.GetVector3XmlString(BBMin));
|
|
|
|
|
HmapXml.SelfClosingTag(sb, indent, "BBMax " + FloatUtil.GetVector3XmlString(BBMax));
|
|
|
|
|
HmapXml.WriteRawArray(sb, InvertImage(MaxHeights, Width, Height), indent, "MaxHeights", "", HmapXml.FormatHexByte, Width);
|
|
|
|
|
HmapXml.WriteRawArray(sb, InvertImage(MinHeights, Width, Height), indent, "MinHeights", "", HmapXml.FormatHexByte, Width);
|
|
|
|
|
}
|
|
|
|
|
public void ReadXml(XmlNode node)
|
|
|
|
|
{
|
2021-04-18 18:39:49 +08:00
|
|
|
|
var endianess = Xml.GetChildInnerText(node, "Endianess");
|
|
|
|
|
if (!string.IsNullOrEmpty(endianess))
|
|
|
|
|
{
|
|
|
|
|
var end = Endianess.BigEndian;
|
|
|
|
|
Enum.TryParse(endianess, out end);
|
|
|
|
|
Endianess = end;
|
|
|
|
|
}
|
2021-04-17 22:30:32 +08:00
|
|
|
|
Width = (ushort)Xml.GetChildUIntAttribute(node, "Width");
|
|
|
|
|
Height = (ushort)Xml.GetChildUIntAttribute(node, "Height");
|
|
|
|
|
BBMin = Xml.GetChildVector3Attributes(node, "BBMin");
|
|
|
|
|
BBMax = Xml.GetChildVector3Attributes(node, "BBMax");
|
|
|
|
|
MaxHeights = InvertImage(Xml.GetChildRawByteArray(node, "MaxHeights"), Width, Height);
|
|
|
|
|
MinHeights = InvertImage(Xml.GetChildRawByteArray(node, "MinHeights"), Width, Height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private byte[] InvertImage(byte[] i, int w, int h)
|
|
|
|
|
{
|
|
|
|
|
//inverts the image vertically
|
|
|
|
|
byte[] o = new byte[i.Length];
|
|
|
|
|
for (int y = 0; y < h; y++)
|
|
|
|
|
{
|
|
|
|
|
int io = y * w;
|
|
|
|
|
int oo = (h - y - 1) * w;
|
|
|
|
|
Buffer.BlockCopy(i, io, o, oo, w);
|
|
|
|
|
}
|
|
|
|
|
return o;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public string GetPGM()
|
|
|
|
|
{
|
|
|
|
|
if (MaxHeights == null) return string.Empty;
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendFormat("P2\n{0} {1}\n255\n", Width, Height);
|
|
|
|
|
|
|
|
|
|
for (int y = Height - 1; y >= 0; y--)
|
|
|
|
|
{
|
|
|
|
|
for (int x = 0; x < Width; x++)
|
|
|
|
|
{
|
|
|
|
|
var h = MaxHeights[y * Width + x];
|
|
|
|
|
sb.Append(h.ToString());
|
|
|
|
|
sb.Append(" ");
|
|
|
|
|
}
|
|
|
|
|
sb.Append("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public struct CompHeader
|
|
|
|
|
{
|
|
|
|
|
public ushort Start;
|
|
|
|
|
public ushort Count;
|
|
|
|
|
public int DataOffset;
|
|
|
|
|
|
|
|
|
|
public void Read(DataReader r)
|
|
|
|
|
{
|
|
|
|
|
Start = r.ReadUInt16();
|
|
|
|
|
Count = r.ReadUInt16();
|
|
|
|
|
DataOffset = r.ReadInt32();
|
|
|
|
|
}
|
|
|
|
|
public void Write(DataWriter w)
|
|
|
|
|
{
|
|
|
|
|
w.Write(Start);
|
|
|
|
|
w.Write(Count);
|
|
|
|
|
w.Write(DataOffset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return Start.ToString() + ", " + Count.ToString() + ", " + DataOffset.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class HmapXml : MetaXmlBase
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public static string GetXml(HeightmapFile hmf)
|
|
|
|
|
{
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine(XmlHeader);
|
|
|
|
|
|
|
|
|
|
if ((hmf != null) && (hmf.MaxHeights != null))
|
|
|
|
|
{
|
|
|
|
|
var name = "Heightmap";
|
|
|
|
|
|
|
|
|
|
OpenTag(sb, 0, name);
|
|
|
|
|
|
|
|
|
|
hmf.WriteXml(sb, 1);
|
|
|
|
|
|
|
|
|
|
CloseTag(sb, 0, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class XmlHmap
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public static HeightmapFile GetHeightmap(string xml)
|
|
|
|
|
{
|
|
|
|
|
XmlDocument doc = new XmlDocument();
|
|
|
|
|
doc.LoadXml(xml);
|
|
|
|
|
return GetHeightmap(doc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static HeightmapFile GetHeightmap(XmlDocument doc)
|
|
|
|
|
{
|
|
|
|
|
HeightmapFile hmf = new HeightmapFile();
|
|
|
|
|
hmf.ReadXml(doc.DocumentElement);
|
|
|
|
|
return hmf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|