source: OniSplit/Level/ObjectImporter.cs

Last change on this file was 1114, checked in by iritscen, 5 years ago

Adding OniSplit source code (v0.9.99.0). Many thanks to Neo for all his work over the years.

File size: 16.1 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Xml;
5using Oni.Xml;
6
7namespace Oni.Level
8{
9 using Akira;
10 using Motoko;
11 using Objects;
12 using Physics;
13
14 partial class LevelImporter
15 {
16 private List<ObjectBase> objects;
17 private ObjectLoadContext objectLoadContext;
18
19 private void ReadObjects(XmlReader xml, string basePath)
20 {
21 info.WriteLine("Reading objects...");
22
23 objects = new List<ObjectBase>();
24 objectLoadContext = new ObjectLoadContext(FindSharedInstance, info);
25
26 xml.ReadStartElement("Objects");
27
28 while (xml.IsStartElement())
29 ReadObjectFile(Path.Combine(basePath, xml.ReadElementContentAsString("Import", "")));
30
31 xml.ReadEndElement();
32 }
33
34 private void ReadObjectFile(string filePath)
35 {
36 var basePath = Path.GetDirectoryName(filePath);
37
38 objectLoadContext.BasePath = basePath;
39 objectLoadContext.FilePath = filePath;
40
41 var settings = new XmlReaderSettings {
42 IgnoreWhitespace = true,
43 IgnoreProcessingInstructions = true,
44 IgnoreComments = true
45 };
46
47 using (var xml = XmlReader.Create(filePath, settings))
48 {
49 xml.ReadStartElement("Oni");
50
51 switch (xml.LocalName)
52 {
53 case "Objects":
54 objects.AddRange(ReadObjects(xml));
55 break;
56
57 case "Particles":
58 level.particles.AddRange(ReadParticles(xml, basePath));
59 break;
60
61 case "Characters":
62 level.characters.AddRange(ReadCharacters(xml, basePath));
63 break;
64
65 case "Physics":
66 ReadPhysics(xml, basePath);
67 break;
68
69 case "Corpses":
70 case "CRSA":
71 level.corpses.AddRange(ReadCorpses(xml, basePath));
72 break;
73
74 default:
75 error.WriteLine("Unknown object file type {0}", xml.LocalName);
76 xml.Skip();
77 break;
78 }
79
80 xml.ReadEndElement();
81 }
82 }
83
84 private IEnumerable<ObjectBase> ReadObjects(XmlReader xml)
85 {
86 if (xml.SkipEmpty())
87 yield break;
88
89 xml.ReadStartElement();
90
91 while (xml.IsStartElement())
92 {
93 ObjectBase obj;
94
95 switch (xml.LocalName)
96 {
97 case "CHAR":
98 case "Character":
99 obj = new Character();
100 break;
101 case "WEAP":
102 case "Weapon":
103 obj = new Weapon();
104 break;
105 case "PART":
106 case "Particle":
107 obj = new Particle();
108 break;
109 case "PWRU":
110 case "PowerUp":
111 obj = new PowerUp();
112 break;
113 case "FLAG":
114 case "Flag":
115 obj = new Flag();
116 break;
117 case "DOOR":
118 case "Door":
119 obj = new Door();
120 break;
121 case "CONS":
122 case "Console":
123 obj = new Console();
124 break;
125 case "FURN":
126 case "Furniture":
127 obj = new Furniture();
128 break;
129 case "TURR":
130 case "Turret":
131 obj = new Turret();
132 break;
133 case "SNDG":
134 case "Sound":
135 obj = new Sound();
136 break;
137 case "TRIG":
138 case "Trigger":
139 obj = new Trigger();
140 break;
141 case "TRGV":
142 case "TriggerVolume":
143 obj = new TriggerVolume();
144 break;
145 case "NEUT":
146 case "Neutral":
147 obj = new Neutral();
148 break;
149 case "PATR":
150 case "Patrol":
151 obj = new PatrolPath();
152 break;
153 default:
154 error.WriteLine("Unknonw object type {0}", xml.LocalName);
155 xml.Skip();
156 continue;
157 }
158
159 obj.Read(xml, objectLoadContext);
160
161 var gunkObject = obj as GunkObject;
162
163 if (gunkObject != null && gunkObject.GunkClass == null)
164 continue;
165
166 yield return obj;
167 }
168
169 xml.ReadEndElement();
170 }
171
172 private void ImportGunkObjects()
173 {
174 int nextObjectId = 1;
175
176 foreach (var obj in objects)
177 {
178 obj.ObjectId = nextObjectId++;
179
180 if (obj is Door)
181 ImportDoor((Door)obj);
182 else if (obj is Furniture)
183 ImportFurniture((Furniture)obj);
184 else if (obj is GunkObject)
185 ImportGunkObject((GunkObject)obj, GunkFlags.NoOcclusion);
186 }
187 }
188
189 private void ImportFurniture(Furniture furniture)
190 {
191 ImportGunkObject(furniture, GunkFlags.NoOcclusion | GunkFlags.Furniture);
192
193 foreach (var particle in furniture.Class.Geometry.Particles)
194 ImportParticle(furniture.ParticleTag, furniture.Transform, particle);
195 }
196
197 private void ImportGunkObject(GunkObject gunkObject, GunkFlags flags)
198 {
199 foreach (var node in gunkObject.GunkClass.GunkNodes)
200 ImportGunkNode(gunkObject.GunkId, gunkObject.Transform, node.Flags | flags, node.Geometry);
201 }
202
203 private void ImportDoor(Door door)
204 {
205 var placement = door.Transform;
206
207 float minY = 0.0f;
208 float minX = 0.0f;
209 float maxX = 0.0f;
210
211 var geometryTransform = Matrix.CreateScale(door.Class.Animation.Keys[0].Scale) * Matrix.CreateRotationX(-MathHelper.HalfPi);
212
213 foreach (var node in door.Class.GunkNodes)
214 {
215 var bbox = BoundingBox.CreateFromPoints(Vector3.Transform(node.Geometry.Points, ref geometryTransform));
216
217 minY = Math.Min(minY, bbox.Min.Y);
218 minX = Math.Min(minX, bbox.Min.X);
219 maxX = Math.Max(maxX, bbox.Max.X);
220 }
221
222 placement.Translation -= Vector3.UnitY * minY;
223
224 float xOffset;
225 int sides;
226
227 if ((door.Flags & DoorFlags.DoubleDoor) == 0)
228 {
229 xOffset = 0.0f;
230 sides = 1;
231 }
232 else
233 {
234 xOffset = (maxX - minX) / 2.0f;
235 sides = 2;
236 }
237
238 for (int side = 0; side < sides; side++)
239 {
240 Matrix origin, gunkTransform;
241
242 if (side == 0)
243 {
244 var m1 = Matrix.CreateTranslation(xOffset, 0.0f, 0.0f) * placement;
245 origin = geometryTransform * m1;
246 gunkTransform = origin;
247 }
248 else
249 {
250 var m2 = Matrix.CreateTranslation(-xOffset, 0.0f, 0.0f) * placement;
251 origin = Matrix.CreateRotationY(MathHelper.Pi) * geometryTransform * m2;
252 gunkTransform = geometryTransform * Matrix.CreateRotationY(MathHelper.Pi) * m2;
253 }
254
255 var scriptId = door.ScriptId | (side << 12);
256 var geometries = ImportDoorGeometry(door, side);
257
258 level.physics.Add(new ObjectSetup {
259 Name = string.Format("door_{0}", scriptId),
260 Flags = ObjectSetupFlags.FaceCollision,
261 DoorScriptId = scriptId,
262 Origin = origin,
263 Geometries = geometries
264 });
265
266 foreach (var geometry in geometries)
267 ImportGunkNode(door.GunkId, gunkTransform, GunkFlags.NoDecals | GunkFlags.NoCollision, geometry);
268 }
269 }
270
271 private Geometry[] ImportDoorGeometry(Door door, int side)
272 {
273 InstanceDescriptor overrideTexture = null;
274
275 if (!string.IsNullOrEmpty(door.Textures[side]))
276 overrideTexture = FindSharedInstance(TemplateTag.TXMP, door.Textures[side]);
277
278 var nodes = door.Class.Geometry.Geometries;
279 var geometries = new Geometry[nodes.Length];
280
281 for (int i = 0; i < nodes.Length; i++)
282 {
283 var node = nodes[i];
284
285 var geometry = new Geometry {
286 Points = node.Geometry.Points,
287 TexCoords = node.Geometry.TexCoords,
288 Normals = node.Geometry.Normals,
289 Triangles = node.Geometry.Triangles
290 };
291
292 if (overrideTexture != null)
293 {
294 geometry.Texture = overrideTexture;
295 geometry.TextureName = overrideTexture.Name;
296 }
297 else if (node.Geometry.Texture != null)
298 {
299 geometry.TextureName = node.Geometry.Texture.Name;
300 }
301
302 geometries[i] = geometry;
303 }
304
305 return geometries;
306 }
307
308 private void WriteObjects()
309 {
310 ObjcDatWriter.Write(objects, outputDirPath);
311 }
312
313 private IEnumerable<Corpse> ReadCorpses(XmlReader xml, string basePath)
314 {
315 var fileName = Path.GetFileName(objectLoadContext.FilePath);
316
317 var isOldFormat = xml.IsStartElement("CRSA");
318 int readCount = 0, fixedCount = 0, usedCount = 0;
319
320 if (isOldFormat)
321 {
322 xml.ReadStartElement("CRSA");
323
324 if (xml.IsStartElement("FixedCount"))
325 fixedCount = xml.ReadElementContentAsInt("FixedCount", "");
326
327 if (xml.IsStartElement("UsedCount"))
328 usedCount = xml.ReadElementContentAsInt("UsedCount", "");
329
330 if (usedCount < fixedCount)
331 {
332 error.WriteLine("There are more fixed corpses ({0}) than used corpses ({1}) - assuming fixed = used", fixedCount, usedCount);
333 fixedCount = usedCount;
334 }
335 }
336
337 xml.ReadStartElement("Corpses");
338
339 while (xml.IsStartElement())
340 {
341 var corpse = new Corpse();
342 corpse.IsFixed = isOldFormat && readCount < fixedCount;
343 corpse.IsUsed = !isOldFormat || readCount < usedCount;
344 corpse.FileName = fileName;
345
346 if (xml.IsEmptyElement)
347 {
348 corpse.IsUsed = false;
349 }
350
351 if (!corpse.IsUsed)
352 {
353 xml.Skip();
354 }
355 else if (xml.LocalName == "Corpse" || xml.LocalName == "CRSACorpse")
356 {
357 xml.ReadStartElement();
358
359 if (!isOldFormat)
360 {
361 if (xml.IsStartElement("CanDelete"))
362 {
363 corpse.IsFixed = false;
364 xml.Skip();
365 }
366 else
367 {
368 corpse.IsFixed = true;
369 }
370 }
371
372 if (xml.IsStartElement("Class") || xml.IsStartElement("CharacterClass"))
373 corpse.CharacterClass = xml.ReadElementContentAsString();
374
375 if (string.IsNullOrEmpty(corpse.CharacterClass))
376 {
377 corpse.IsUsed = false;
378 corpse.IsFixed = false;
379 }
380
381 xml.ReadStartElement("Transforms");
382
383 for (int j = 0; j < corpse.Transforms.Length; j++)
384 {
385 if (xml.IsStartElement("Matrix4x3"))
386 corpse.Transforms[j] = xml.ReadElementContentAsMatrix43("Matrix4x3");
387 else if (xml.IsStartElement("Matrix"))
388 corpse.Transforms[j] = xml.ReadElementContentAsMatrix43("Matrix");
389 }
390
391 xml.ReadEndElement();
392
393 if (xml.IsStartElement("BoundingBox"))
394 {
395 xml.ReadStartElement("BoundingBox");
396 corpse.BoundingBox.Min = xml.ReadElementContentAsVector3("Min");
397 corpse.BoundingBox.Max = xml.ReadElementContentAsVector3("Max");
398 xml.ReadEndElement();
399 }
400 else
401 {
402 corpse.BoundingBox.Min = corpse.Transforms[0].Translation;
403 corpse.BoundingBox.Max = corpse.Transforms[0].Translation;
404 corpse.BoundingBox.Inflate(new Vector3(10.0f, 5.0f, 10.0f));
405 }
406
407 xml.ReadEndElement();
408 }
409 else
410 {
411 var filePath = xml.ReadElementContentAsString("Import", "");
412 filePath = Path.Combine(basePath, filePath);
413
414 using (var reader = new BinaryReader(filePath))
415 {
416 corpse.FileName = Path.GetFileName(filePath);
417 corpse.IsUsed = true;
418 corpse.IsFixed = true;
419
420 corpse.CharacterClass = reader.ReadString(128);
421 reader.Skip(4);
422
423 for (int i = 0; i < corpse.Transforms.Length; i++)
424 corpse.Transforms[i] = reader.ReadMatrix4x3();
425
426 corpse.BoundingBox = reader.ReadBoundingBox();
427 }
428 }
429
430 readCount++;
431 yield return corpse;
432 }
433
434 if (readCount < usedCount)
435 {
436 error.WriteLine("{0} corpses were expected but only {1} have been read", usedCount, readCount);
437 }
438
439 info.WriteLine("Read {0} corpses", readCount);
440 }
441
442 private InstanceFileManager fileManager;
443
444 private InstanceDescriptor FindSharedInstance(TemplateTag tag, string name, ObjectLoadContext loadContext)
445 {
446 if (!name.EndsWith(".oni", StringComparison.OrdinalIgnoreCase))
447 return FindSharedInstance(tag, name);
448
449 string filePath = Path.GetFullPath(Path.Combine(loadContext.BasePath, name));
450
451 if (File.Exists(filePath))
452 {
453 if (fileManager == null)
454 fileManager = new InstanceFileManager();
455
456 return fileManager.OpenFile(filePath).Descriptors[0];
457 }
458
459 filePath = Path.GetFullPath(Path.Combine(sharedPath, name));
460
461 if (File.Exists(filePath))
462 {
463 var file = sharedManager.OpenFile(filePath);
464
465 return file.Descriptors[0];
466 }
467
468 error.WriteLine("Could not find {0}", name);
469
470 return null;
471 }
472 }
473}
Note: See TracBrowser for help on using the repository browser.