1 /// MDB file parsing
2 module nwn.mdb;
3 
4 import std.stdint;
5 import std.typecons;
6 import nwnlibd.parseutils;
7 
8 /// MDB file parsing
9 class Mdb {
10 	///
11 	static align(1) struct Header {
12 		static assert(this.sizeof == 12);
13 		align(1):
14 		char[4] type;
15 		uint16_t majorVersion;
16 		uint16_t minorVersion;
17 		uint32_t packets_length;
18 	}
19 	///
20 	Header header;
21 
22 	///
23 	enum PacketType: char[4] {
24 		RIGD = "RIGD", /// Rigid body
25 		SKIN = "SKIN", /// Skin Vertex
26 		COL2 = "COL2", /// Coarse collision mesh
27 		COL3 = "COL3", /// Precise collision mesh
28 		HOOK = "HOOK", /// Hook point
29 		WALK = "WALK", /// Walk Mesh
30 		COLS = "COLS",
31 		TRRN = "TRRN",
32 		HELM = "HELM",
33 		HAIR = "HAIR"
34 	}
35 
36 	///
37 	static align(1) struct PacketKey {
38 		static assert(this.sizeof == 8);
39 		align(1):
40 		PacketType type;
41 		uint32_t offset;
42 	}
43 	///
44 	PacketKey[] packet_keys;
45 
46 	///
47 	static struct Packet {
48 		///
49 		PacketType type;
50 		///
51 		package ubyte[] rawData;
52 
53 		///
54 		package this(PacketType type, in ubyte[] rawData){
55 			this.type = type;
56 			this.rawData = rawData.dup;
57 		}
58 
59 		///
60 		auto get(PacketType type)(){
61 			assert(type == this.type, "Wrong packet type");
62 
63 			scope(exit) rawData = null;
64 
65 			static if(type == PacketType.WALK) return MdbWALK(rawData);
66 			else static assert(0, "MDB packet "~type~" not implemented");
67 		}
68 	}
69 	///
70 	Packet[] packets;
71 
72 
73 
74 	///
75 	this(in ubyte[] data){
76 		auto cr = new ChunkReader(data);
77 		header = cr.read!Header;
78 
79 		packet_keys = cr.readArray!PacketKey(header.packets_length).dup;
80 
81 		packets.length = header.packets_length;
82 		foreach(ref packet ; packets){
83 			auto type = cr.read!PacketType;
84 			auto size = cr.read!uint32_t;
85 
86 			packet = Packet(type, cr.readArray(size));
87 		}
88 
89 		assert(cr.bytesLeft == 0, "Remaining data");
90 	}
91 
92 }
93 
94 private align(1) union MdbVertex {
95 	static assert(this.sizeof == 12);
96 	align(1):
97 	float[3] position;
98 
99 	private struct Xyz{ float x, y, z; }
100 	Xyz _xyz;
101 	alias _xyz this;
102 }
103 
104 private align(1) struct MdbTriangle {
105 	static assert(this.sizeof == 6);
106 	align(1):
107 	uint16_t[3] vertices;
108 }
109 
110 
111 ///
112 struct MdbWALK {
113 
114 	///
115 	static align(1) struct Header {
116 		char[32] name;
117 		uint32_t ui_flags;
118 		uint32_t vertices_length;
119 		uint32_t triangles_length;
120 	}
121 	///
122 	Header header;
123 	///
124 	MdbVertex[] vertices;
125 
126 	///
127 	static align(1) struct MdbWalkTriangle {
128 		align(1):
129 		MdbTriangle tri;
130 		alias tri this;
131 
132 		enum Flags : uint32_t {
133 			walkable = 0b00000000_00000000_00000000_00000001,
134 			_res1    = 0b00000000_00000000_00000000_00000010,
135 			_res2    = 0b00000000_00000000_00000000_00000100,
136 			dirt     = 0b00000000_00000000_00000000_00001000,
137 			grass    = 0b00000000_00000000_00000000_00010000,
138 			stone    = 0b00000000_00000000_00000000_00100000,
139 			wood     = 0b00000000_00000000_00000000_01000000,
140 			carpet   = 0b00000000_00000000_00000000_10000000,
141 			metal    = 0b00000000_00000000_00000001_00000000,
142 			swamp    = 0b00000000_00000000_00000010_00000000,
143 			mud      = 0b00000000_00000000_00000100_00000000,
144 			leaves   = 0b00000000_00000000_00001000_00000000,
145 			water    = 0b00000000_00000000_00010000_00000000,
146 			puddles  = 0b00000000_00000000_00100000_00000000,
147 			_res3    = 0b11111111_11111111_11000000_00000000,
148 		}
149 		uint32_t flags;
150 	}
151 	///
152 	MdbWalkTriangle[] triangles;
153 
154 	///
155 	this(in ubyte[] rawData){
156 		auto cr = ChunkReader(rawData);
157 
158 		header = cr.read!Header;
159 		vertices = cr.readArray!MdbVertex(header.vertices_length).dup;
160 		triangles = cr.readArray!MdbWalkTriangle(header.triangles_length).dup;
161 
162 		assert(cr.bytesLeft == 0, "Remaining data");
163 	}
164 
165 	///
166 	void toObj(string filePath) const {
167 		import std.stdio;
168 		auto obj = File(filePath, "w");
169 		obj.writeln("o ", filePath);
170 
171 		foreach(ref v ; vertices){
172 			writeln("z=", *cast(uint16_t*)&v.z);
173 			obj.writefln("v %(%f %)", v.position);
174 		}
175 		foreach(ref t ; triangles){
176 			obj.writefln("f %s %s %s", t.vertices[0] + 1, t.vertices[1] + 1, t.vertices[2] + 1);
177 		}
178 	}
179 
180 }
181 
182 
183 unittest {
184 	const balcony = cast(ubyte[])import("PLC_MC_BALCONY3.MDB");
185 
186 	auto mdb = new Mdb(balcony);
187 	foreach(ref packet ; mdb.packets){
188 		if(packet.type == Mdb.PacketType.WALK){
189 			auto wm = packet.get!(Mdb.PacketType.WALK);
190 		}
191 	}
192 }