1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
using System.IO; |
4 |
using System.Xml; |
5 |
|
6 |
namespace Oni.Level |
7 |
{ |
8 |
using Akira; |
9 |
using Metadata; |
10 |
using Motoko; |
11 |
using Physics; |
12 |
|
13 |
partial class LevelImporter |
14 |
{ |
15 |
private List<Dae.Scene> roomScenes; |
16 |
private PolygonMesh model; |
17 |
private AkiraDaeReader daeReader; |
18 |
|
19 |
private void ReadModel(XmlReader xml, string basePath) |
20 |
{ |
21 |
xml.ReadStartElement("Environment"); |
22 |
|
23 |
xml.ReadStartElement("Model"); |
24 |
|
25 |
daeReader = new AkiraDaeReader(); |
26 |
model = daeReader.Mesh; |
27 |
level.model = model; |
28 |
|
29 |
info.WriteLine("Reading environment..."); |
30 |
|
31 |
while (xml.IsStartElement()) |
32 |
{ |
33 |
switch (xml.LocalName) |
34 |
{ |
35 |
case "Import": |
36 |
case "Scene": |
37 |
ImportModelScene(xml, basePath); |
38 |
break; |
39 |
|
40 |
case "Object": |
41 |
xml.Skip(); |
42 |
break; |
43 |
|
44 |
case "Camera": |
45 |
ReadCamera(xml, basePath); |
46 |
break; |
47 |
|
48 |
case "Texture": |
49 |
textureImporter.ReadOptions(xml, basePath); |
50 |
break; |
51 |
|
52 |
default: |
53 |
error.WriteLine("Unknown element {0}", xml.LocalName); |
54 |
xml.Skip(); |
55 |
break; |
56 |
} |
57 |
} |
58 |
|
59 |
info.WriteLine("Reading rooms..."); |
60 |
|
61 |
roomScenes = new List<Dae.Scene>(); |
62 |
|
63 |
xml.ReadEndElement(); |
64 |
xml.ReadStartElement("Rooms"); |
65 |
|
66 |
while (xml.IsStartElement("Import")) |
67 |
{ |
68 |
string filePath = xml.GetAttribute("Path"); |
69 |
|
70 |
if (filePath == null) |
71 |
filePath = xml.ReadElementContentAsString(); |
72 |
else |
73 |
xml.Skip(); |
74 |
|
75 |
filePath = Path.Combine(basePath, filePath); |
76 |
|
77 |
roomScenes.Add(LoadScene(filePath)); |
78 |
} |
79 |
|
80 |
xml.ReadEndElement(); |
81 |
|
82 |
if (xml.IsStartElement("Textures")) |
83 |
ReadTextures(xml, basePath); |
84 |
|
85 |
xml.ReadEndElement(); |
86 |
} |
87 |
|
88 |
private class NodePropertiesReader |
89 |
{ |
90 |
private readonly string basePath; |
91 |
private readonly TextWriter error; |
92 |
public readonly Dictionary<string, AkiraDaeNodeProperties> properties = new Dictionary<string, AkiraDaeNodeProperties>(StringComparer.Ordinal); |
93 |
|
94 |
public NodePropertiesReader(string basePath, TextWriter error) |
95 |
{ |
96 |
this.basePath = basePath; |
97 |
this.error = error; |
98 |
} |
99 |
|
100 |
public Dictionary<string, AkiraDaeNodeProperties> Properties |
101 |
{ |
102 |
get { return properties; } |
103 |
} |
104 |
|
105 |
public void ReadScene(XmlReader xml, Dae.Node scene) |
106 |
{ |
107 |
var nodeProperties = new ObjectDaeNodeProperties(); |
108 |
properties.Add(scene.Id, nodeProperties); |
109 |
|
110 |
while (xml.IsStartElement()) |
111 |
{ |
112 |
switch (xml.LocalName) |
113 |
{ |
114 |
case "GunkFlags": |
115 |
nodeProperties.GunkFlags = xml.ReadElementContentAsEnum<GunkFlags>(); |
116 |
break; |
117 |
case "ScriptId": |
118 |
nodeProperties.ScriptId = xml.ReadElementContentAsInt(); |
119 |
break; |
120 |
case "Node": |
121 |
ReadNode(xml, scene, nodeProperties); |
122 |
break; |
123 |
default: |
124 |
xml.Skip(); |
125 |
break; |
126 |
} |
127 |
} |
128 |
} |
129 |
|
130 |
private void ReadNode(XmlReader xml, Dae.Node parentNode, ObjectDaeNodeProperties parentNodeProperties) |
131 |
{ |
132 |
string id = xml.GetAttribute("Id"); |
133 |
|
134 |
if (string.IsNullOrEmpty(id)) |
135 |
{ |
136 |
error.Write("Each import node must have an Id attribute"); |
137 |
xml.Skip(); |
138 |
return; |
139 |
} |
140 |
|
141 |
var nodeProperties = new ObjectDaeNodeProperties { |
142 |
GunkFlags = parentNodeProperties.GunkFlags, |
143 |
ScriptId = parentNodeProperties.ScriptId, |
144 |
HasPhysics = parentNodeProperties.HasPhysics |
145 |
}; |
146 |
|
147 |
properties.Add(id, nodeProperties); |
148 |
|
149 |
xml.ReadStartElement("Node"); |
150 |
|
151 |
while (xml.IsStartElement()) |
152 |
{ |
153 |
switch (xml.LocalName) |
154 |
{ |
155 |
case "GunkFlags": |
156 |
nodeProperties.GunkFlags |= xml.ReadElementContentAsEnum<GunkFlags>(); |
157 |
break; |
158 |
case "ScriptId": |
159 |
nodeProperties.ScriptId = xml.ReadElementContentAsInt(); |
160 |
break; |
161 |
case "Physics": |
162 |
nodeProperties.PhysicsType = xml.ReadElementContentAsEnum<ObjectPhysicsType>(); |
163 |
nodeProperties.HasPhysics = true; |
164 |
break; |
165 |
case "ObjectFlags": |
166 |
nodeProperties.ObjectFlags = xml.ReadElementContentAsEnum<ObjectSetupFlags>(); |
167 |
nodeProperties.HasPhysics = true; |
168 |
break; |
169 |
case "Animation": |
170 |
nodeProperties.Animations.Add(ReadAnimationClip(xml)); |
171 |
nodeProperties.HasPhysics = true; |
172 |
break; |
173 |
case "Particles": |
174 |
nodeProperties.Particles.AddRange(ReadParticles(xml, basePath)); |
175 |
nodeProperties.HasPhysics = true; |
176 |
break; |
177 |
default: |
178 |
error.WriteLine("Unknown physics object element {0}", xml.LocalName); |
179 |
xml.Skip(); |
180 |
break; |
181 |
} |
182 |
} |
183 |
|
184 |
xml.ReadEndElement(); |
185 |
} |
186 |
|
187 |
private ObjectAnimationClip ReadAnimationClip(XmlReader xml) |
188 |
{ |
189 |
var animClip = new ObjectAnimationClip(xml.GetAttribute("Name")); |
190 |
|
191 |
if (!xml.SkipEmpty()) |
192 |
{ |
193 |
xml.ReadStartElement(); |
194 |
|
195 |
while (xml.IsStartElement()) |
196 |
{ |
197 |
switch (xml.LocalName) |
198 |
{ |
199 |
case "Start": |
200 |
animClip.Start = xml.ReadElementContentAsInt(); |
201 |
break; |
202 |
case "Stop": |
203 |
animClip.Stop = xml.ReadElementContentAsInt(); |
204 |
break; |
205 |
case "End": |
206 |
animClip.End = xml.ReadElementContentAsInt(); |
207 |
break; |
208 |
case "Flags": |
209 |
animClip.Flags = xml.ReadElementContentAsEnum<ObjectAnimationFlags>(); |
210 |
break; |
211 |
default: |
212 |
error.WriteLine("Unknown object animation property {0}", xml.LocalName); |
213 |
xml.Skip(); |
214 |
break; |
215 |
} |
216 |
} |
217 |
|
218 |
xml.ReadEndElement(); |
219 |
} |
220 |
|
221 |
return animClip; |
222 |
} |
223 |
} |
224 |
|
225 |
private void ImportModelScene(XmlReader xml, string basePath) |
226 |
{ |
227 |
var filePath = Path.Combine(basePath, xml.GetAttribute("Path")); |
228 |
var scene = LoadScene(filePath); |
229 |
|
230 |
var propertiesReader = new NodePropertiesReader(basePath, error); |
231 |
|
232 |
if (!xml.SkipEmpty()) |
233 |
{ |
234 |
xml.ReadStartElement(); |
235 |
propertiesReader.ReadScene(xml, scene); |
236 |
xml.ReadEndElement(); |
237 |
} |
238 |
|
239 |
daeReader.ReadScene(scene, propertiesReader.Properties); |
240 |
|
241 |
if (propertiesReader.Properties.Values.Any(p => p.HasPhysics)) |
242 |
{ |
243 |
var imp = new ObjectDaeImporter(textureImporter, propertiesReader.Properties); |
244 |
|
245 |
imp.Import(scene); |
246 |
|
247 |
foreach (var node in imp.Nodes.Where(n => n.Geometries.Length > 0)) |
248 |
{ |
249 |
var setup = new ObjectSetup { |
250 |
Name = node.Name, |
251 |
FileName = node.FileName, |
252 |
ScriptId = node.ScriptId, |
253 |
Flags = node.Flags, |
254 |
PhysicsType = ObjectPhysicsType.Animated |
255 |
}; |
256 |
|
257 |
setup.Geometries = node.Geometries |
258 |
.Where(n => (n.Flags & GunkFlags.Invisible) == 0) |
259 |
.Select(n => n.Geometry.Name).ToArray(); |
260 |
|
261 |
foreach (var nodeGeometry in node.Geometries.Where(g => (g.Flags & GunkFlags.Invisible) == 0)) |
262 |
{ |
263 |
var writer = new DatWriter(); |
264 |
GeometryDatWriter.Write(nodeGeometry.Geometry, writer.ImporterFile); |
265 |
writer.Write(outputDirPath); |
266 |
} |
267 |
|
268 |
setup.Position = Vector3.Zero; |
269 |
setup.Orientation = Quaternion.Identity; |
270 |
setup.Scale = 1.0f; |
271 |
setup.Origin = Matrix.CreateFromQuaternion(setup.Orientation) |
272 |
* Matrix.CreateScale(setup.Scale) |
273 |
* Matrix.CreateTranslation(setup.Position); |
274 |
|
275 |
//int i = 0; |
276 |
|
277 |
foreach (var animation in node.Animations) |
278 |
{ |
279 |
//if (nodes.Count > 1) |
280 |
// animation.Name += i.ToString("d2", CultureInfo.InvariantCulture); |
281 |
|
282 |
if ((animation.Flags & ObjectAnimationFlags.Local) == 0) |
283 |
{ |
284 |
//animation.Scale = Matrix.CreateScale(setup.Scale); |
285 |
|
286 |
foreach (var key in animation.Keys) |
287 |
{ |
288 |
key.Rotation = setup.Orientation * key.Rotation; |
289 |
key.Translation += setup.Position; |
290 |
} |
291 |
} |
292 |
|
293 |
if ((animation.Flags & ObjectAnimationFlags.AutoStart) != 0) |
294 |
{ |
295 |
setup.Animation = animation; |
296 |
setup.PhysicsType = ObjectPhysicsType.Animated; |
297 |
} |
298 |
|
299 |
var writer = new DatWriter(); |
300 |
writer.BeginImport(); |
301 |
ObjectDatWriter.WriteAnimation(animation, writer); |
302 |
writer.Write(outputDirPath); |
303 |
} |
304 |
|
305 |
if (setup.Animation == null && node.Animations.Length > 0) |
306 |
{ |
307 |
setup.Animation = node.Animations[0]; |
308 |
} |
309 |
|
310 |
if (setup.Animation != null) |
311 |
{ |
312 |
var frame0 = setup.Animation.Keys[0]; |
313 |
|
314 |
setup.Scale = frame0.Scale.X; |
315 |
setup.Orientation = frame0.Rotation; |
316 |
setup.Position = frame0.Translation; |
317 |
} |
318 |
|
319 |
level.physics.Add(setup); |
320 |
} |
321 |
} |
322 |
} |
323 |
|
324 |
private void ImportModel(string basePath) |
325 |
{ |
326 |
info.WriteLine("Importing objects..."); |
327 |
ImportGunkObjects(); |
328 |
|
329 |
info.WriteLine("Importing textures..."); |
330 |
ImportModelTextures(); |
331 |
|
332 |
info.WriteLine("Generating grids..."); |
333 |
|
334 |
string gridFilePath = Path.Combine(basePath, string.Format("temp/grids/{0}_grids.dae", level.name)); |
335 |
|
336 |
var gridBuilder = new RoomGridBuilder(roomScenes[0], model); |
337 |
gridBuilder.Build(); |
338 |
AkiraDaeWriter.WriteRooms(gridBuilder.Mesh, gridFilePath); |
339 |
|
340 |
daeReader.ReadScene(Dae.Reader.ReadFile(gridFilePath), new Dictionary<string, AkiraDaeNodeProperties>()); |
341 |
|
342 |
info.WriteLine("Writing environment..."); |
343 |
|
344 |
var writer = new DatWriter(); |
345 |
AkiraDatWriter.Write(model, writer, level.name, debug); |
346 |
writer.Write(outputDirPath); |
347 |
} |
348 |
|
349 |
private void ImportGunkNode(int gunkId, Matrix transform, GunkFlags flags, Geometry geometry) |
350 |
{ |
351 |
ImportGunk(gunkId, transform, flags, geometry, null); |
352 |
} |
353 |
|
354 |
private void ImportGunk(int gunkId, Matrix transform, GunkFlags flags, Geometry geometry, string textureName) |
355 |
{ |
356 |
TextureFormat? textureFormat = null; |
357 |
|
358 |
if (geometry.Texture != null) |
359 |
{ |
360 |
Texture texture = null; |
361 |
|
362 |
if (!geometry.Texture.IsPlaceholder) |
363 |
{ |
364 |
texture = TextureDatReader.ReadInfo(geometry.Texture); |
365 |
} |
366 |
else |
367 |
{ |
368 |
var txmp = FindSharedInstance(TemplateTag.TXMP, geometry.Texture.Name); |
369 |
|
370 |
if (txmp != null) |
371 |
texture = TextureDatReader.ReadInfo(txmp); |
372 |
} |
373 |
|
374 |
if (texture != null) |
375 |
textureFormat = texture.Format; |
376 |
} |
377 |
else |
378 |
{ |
379 |
if (geometry.TextureName != null) |
380 |
{ |
381 |
var options = textureImporter.GetOptions(geometry.TextureName, false); |
382 |
|
383 |
if (options != null) |
384 |
textureFormat = options.Format; |
385 |
} |
386 |
} |
387 |
|
388 |
switch (textureFormat) |
389 |
{ |
390 |
case TextureFormat.BGRA4444: |
391 |
case TextureFormat.BGRA5551: |
392 |
case TextureFormat.RGBA: |
393 |
flags |= GunkFlags.Transparent | GunkFlags.TwoSided | GunkFlags.NoOcclusion; |
394 |
break; |
395 |
} |
396 |
|
397 |
Material material; |
398 |
|
399 |
if (!string.IsNullOrEmpty(textureName)) |
400 |
material = model.Materials.GetMaterial(textureName); |
401 |
else if (!string.IsNullOrEmpty(geometry.TextureName)) |
402 |
material = model.Materials.GetMaterial(geometry.TextureName); |
403 |
else if (geometry.Texture != null) |
404 |
material = model.Materials.GetMaterial(geometry.Texture.Name); |
405 |
else |
406 |
material = model.Materials.GetMaterial("NONE"); |
407 |
|
408 |
int pointIndexBase = model.Points.Count; |
409 |
int texCoordIndexBase = model.TexCoords.Count; |
410 |
|
411 |
model.Points.AddRange(Vector3.Transform(geometry.Points, ref transform)); |
412 |
model.TexCoords.AddRange(geometry.TexCoords); |
413 |
|
414 |
foreach (var quad in Quadify.Do(geometry)) |
415 |
{ |
416 |
var pointIndices = new int[quad.Length]; |
417 |
var texCoordIndices = new int[quad.Length]; |
418 |
var colors = new Imaging.Color[quad.Length]; |
419 |
|
420 |
for (int j = 0; j < quad.Length; j++) |
421 |
{ |
422 |
pointIndices[j] = pointIndexBase + quad[j]; |
423 |
texCoordIndices[j] = texCoordIndexBase + quad[j]; |
424 |
colors[j] = new Imaging.Color(207, 207, 207); |
425 |
} |
426 |
|
427 |
var poly = new Polygon(model, pointIndices) { |
428 |
TexCoordIndices = texCoordIndices, |
429 |
Colors = colors, |
430 |
Material = material, |
431 |
ObjectId = gunkId & 0xffffff, |
432 |
ObjectType = gunkId >> 24 |
433 |
}; |
434 |
|
435 |
poly.Flags |= flags; |
436 |
model.Polygons.Add(poly); |
437 |
} |
438 |
} |
439 |
|
440 |
private void ImportModelTextures() |
441 |
{ |
442 |
int imported = 0; |
443 |
int copied = 0; |
444 |
|
445 |
var copy = new List<InstanceDescriptor>(); |
446 |
|
447 |
foreach (var material in model.Polygons.Select(p => p.Material).Distinct()) |
448 |
{ |
449 |
//if (material.IsMarker) |
450 |
// continue; |
451 |
|
452 |
if (File.Exists(material.ImageFilePath)) |
453 |
{ |
454 |
var options = textureImporter.AddMaterial(material); |
455 |
|
456 |
if (options != null) |
457 |
material.Flags |= options.GunkFlags; |
458 |
|
459 |
imported++; |
460 |
} |
461 |
else |
462 |
{ |
463 |
var txmp = FindSharedInstance(TemplateTag.TXMP, material.Name); |
464 |
|
465 |
if (txmp != null) |
466 |
copy.Add(txmp); |
467 |
} |
468 |
} |
469 |
|
470 |
Parallel.ForEach(copy, txmp => { |
471 |
var texture = TextureDatReader.Read(txmp); |
472 |
|
473 |
if ((texture.Flags & TextureFlags.HasMipMaps) == 0) |
474 |
{ |
475 |
texture.GenerateMipMaps(); |
476 |
TextureDatWriter.Write(texture, outputDirPath); |
477 |
System.Threading.Interlocked.Increment(ref imported); |
478 |
} |
479 |
else |
480 |
{ |
481 |
string sourceFilePath = txmp.File.FilePath; |
482 |
File.Copy(sourceFilePath, Path.Combine(outputDirPath, Path.GetFileName(sourceFilePath)), true); |
483 |
System.Threading.Interlocked.Increment(ref copied); |
484 |
} |
485 |
}); |
486 |
|
487 |
error.WriteLine("Imported {0} textures, copied {1} textures", imported, copied); |
488 |
} |
489 |
|
490 |
private ObjectAnimationClip ReadAnimationClip(XmlReader xml) |
491 |
{ |
492 |
var anim = new ObjectAnimationClip(xml.GetAttribute("Name")); |
493 |
|
494 |
if (!xml.SkipEmpty()) |
495 |
{ |
496 |
xml.ReadStartElement(); |
497 |
|
498 |
while (xml.IsStartElement()) |
499 |
{ |
500 |
switch (xml.LocalName) |
501 |
{ |
502 |
case "Start": |
503 |
anim.Start = xml.ReadElementContentAsInt(); |
504 |
break; |
505 |
case "Stop": |
506 |
anim.Stop = xml.ReadElementContentAsInt(); |
507 |
break; |
508 |
case "End": |
509 |
anim.End = xml.ReadElementContentAsInt(); |
510 |
break; |
511 |
case "Flags": |
512 |
anim.Flags = xml.ReadElementContentAsEnum<ObjectAnimationFlags>(); |
513 |
break; |
514 |
default: |
515 |
error.WriteLine("Unknown object animation parameter {0}", xml.LocalName); |
516 |
xml.Skip(); |
517 |
break; |
518 |
} |
519 |
} |
520 |
|
521 |
xml.ReadEndElement(); |
522 |
} |
523 |
|
524 |
return anim; |
525 |
} |
526 |
} |
527 |
} |