mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-12-14 06:01:12 +00:00
367 lines
14 KiB
C#
367 lines
14 KiB
C#
|
using CodeImp.DoomBuilder.Data;
|
|||
|
using CodeImp.DoomBuilder.GZBuilder.Data;
|
|||
|
using CodeImp.DoomBuilder.Rendering;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Drawing;
|
|||
|
using System.IO;
|
|||
|
|
|||
|
namespace CodeImp.DoomBuilder.GZBuilder.Models
|
|||
|
{
|
|||
|
internal class ModelLoader
|
|||
|
{
|
|||
|
#region ================== Load
|
|||
|
|
|||
|
public static void Load(ModelData mde, List<DataReader> containers)
|
|||
|
{
|
|||
|
if (mde.IsVoxel) LoadKVX(mde, containers);
|
|||
|
else LoadModel(mde, containers);
|
|||
|
}
|
|||
|
|
|||
|
private static void LoadKVX(ModelData mde, List<DataReader> containers)
|
|||
|
{
|
|||
|
mde.Model = new GZModel();
|
|||
|
string unused = string.Empty;
|
|||
|
foreach (string name in mde.ModelNames)
|
|||
|
{
|
|||
|
//find the model
|
|||
|
foreach (DataReader dr in containers)
|
|||
|
{
|
|||
|
Stream ms = dr.GetVoxelData(name, ref unused);
|
|||
|
if (ms == null) continue;
|
|||
|
|
|||
|
//load kvx
|
|||
|
KVXModelLoader.Load(mde, ms);
|
|||
|
|
|||
|
//done
|
|||
|
ms.Close();
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//clear unneeded data
|
|||
|
mde.SkinNames = null;
|
|||
|
mde.ModelNames = null;
|
|||
|
|
|||
|
if (mde.Model.Meshes == null || mde.Model.Meshes.Count == 0)
|
|||
|
{
|
|||
|
mde.Model = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void LoadModel(ModelData mde, List<DataReader> containers)
|
|||
|
{
|
|||
|
mde.Model = new GZModel();
|
|||
|
BoundingBoxSizes bbs = new BoundingBoxSizes();
|
|||
|
ModelLoadResult result = new ModelLoadResult();
|
|||
|
|
|||
|
//load models and textures
|
|||
|
for (int i = 0; i < mde.ModelNames.Count; i++)
|
|||
|
{
|
|||
|
// Use model skins?
|
|||
|
// INFO: Skin MODELDEF property overrides both embedded surface names and ones set using SurfaceSkin MODELDEF property
|
|||
|
Dictionary<int, string> skins = null;
|
|||
|
if (string.IsNullOrEmpty(mde.SkinNames[i]))
|
|||
|
{
|
|||
|
skins = (mde.SurfaceSkinNames[i].Count > 0 ? mde.SurfaceSkinNames[i] : new Dictionary<int, string>());
|
|||
|
}
|
|||
|
|
|||
|
// Load mesh
|
|||
|
MemoryStream ms = LoadFile(containers, mde.ModelNames[i], true);
|
|||
|
if (ms == null)
|
|||
|
{
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": unable to find file.");
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
string ext = Path.GetExtension(mde.ModelNames[i]);
|
|||
|
switch (ext)
|
|||
|
{
|
|||
|
case ".md3":
|
|||
|
if (!string.IsNullOrEmpty(mde.FrameNames[i]))
|
|||
|
{
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": frame names are not supported for MD3 models!");
|
|||
|
continue;
|
|||
|
}
|
|||
|
result = MD3ModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i]);
|
|||
|
break;
|
|||
|
case ".md2":
|
|||
|
result = MD2ModelLoader.Load(ref bbs, ms, mde.FrameIndices[i], mde.FrameNames[i]);
|
|||
|
break;
|
|||
|
case ".3d":
|
|||
|
result = UnrealModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i], mde.ModelNames[i], containers);
|
|||
|
break;
|
|||
|
case ".obj":
|
|||
|
// OBJ doesn't support frames, so print out an error
|
|||
|
if (mde.FrameIndices[i] > 0)
|
|||
|
{
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Trying to load frame " + mde.FrameIndices[i] + " of model \"" + mde.ModelNames[i] + "\", but OBJ doesn't support frames!");
|
|||
|
continue;
|
|||
|
}
|
|||
|
result = OBJModelLoader.Load(ref bbs, skins, ms, mde.ModelNames[i]);
|
|||
|
break;
|
|||
|
case ".iqm":
|
|||
|
if (!string.IsNullOrEmpty(mde.FrameNames[i]))
|
|||
|
{
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": frame names are not supported for IQM models!");
|
|||
|
continue;
|
|||
|
}
|
|||
|
result = IQMModelLoader.Load(ref bbs, skins, ms, mde.FrameIndices[i]);
|
|||
|
break;
|
|||
|
default:
|
|||
|
result.Errors = "model format is not supported";
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
ms.Close();
|
|||
|
if (result == null)
|
|||
|
continue;
|
|||
|
|
|||
|
//got errors?
|
|||
|
if (!String.IsNullOrEmpty(result.Errors))
|
|||
|
{
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": " + result.Errors);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//add loaded data to ModeldefEntry
|
|||
|
mde.Model.Meshes.AddRange(result.Meshes);
|
|||
|
|
|||
|
//load texture
|
|||
|
List<string> errors = new List<string>();
|
|||
|
|
|||
|
// Texture not defined in MODELDEF?
|
|||
|
if (skins != null)
|
|||
|
{
|
|||
|
//try to use model's own skins
|
|||
|
for (int m = 0; m < result.Meshes.Count; m++)
|
|||
|
{
|
|||
|
// biwa. Makes sure to add a dummy texture if the MODELDEF skin definition is erroneous
|
|||
|
if (m >= result.Skins.Count)
|
|||
|
{
|
|||
|
errors.Add("no skin defined for mesh " + m + ".");
|
|||
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (string.IsNullOrEmpty(result.Skins[m]))
|
|||
|
{
|
|||
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|||
|
errors.Add("texture not found in MODELDEF or model skin.");
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
string path = result.Skins[m].Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|||
|
|
|||
|
if (!String.IsNullOrEmpty(mde.Path))
|
|||
|
path = Path.Combine(mde.Path, path);
|
|||
|
|
|||
|
Texture t = GetTexture(containers, path);
|
|||
|
|
|||
|
if (t != null)
|
|||
|
{
|
|||
|
mde.Model.Textures.Add(t);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
// That didn't work, let's try to load the texture without the additional path
|
|||
|
path = result.Skins[m].Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|||
|
t = GetTexture(containers, path);
|
|||
|
|
|||
|
if (t == null)
|
|||
|
{
|
|||
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|||
|
errors.Add("unable to load skin \"" + path + "\"");
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
mde.Model.Textures.Add(t);
|
|||
|
}
|
|||
|
}
|
|||
|
//Try to use texture loaded from MODELDEFS
|
|||
|
else
|
|||
|
{
|
|||
|
Texture t = GetTexture(containers, mde.SkinNames[i]);
|
|||
|
|
|||
|
if (t == null)
|
|||
|
{
|
|||
|
mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
|
|||
|
errors.Add("unable to load skin \"" + mde.SkinNames[i] + "\"");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
mde.Model.Textures.Add(t);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//report errors
|
|||
|
if (errors.Count > 0)
|
|||
|
{
|
|||
|
foreach (string e in errors)
|
|||
|
General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": " + e);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//clear unneeded data
|
|||
|
mde.SkinNames = null;
|
|||
|
mde.ModelNames = null;
|
|||
|
|
|||
|
if (mde.Model.Meshes == null || mde.Model.Meshes.Count == 0)
|
|||
|
{
|
|||
|
mde.Model = null;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//scale bbs
|
|||
|
bbs.MaxX = (int)(bbs.MaxX * mde.Scale.X);
|
|||
|
bbs.MinX = (int)(bbs.MinX * mde.Scale.X);
|
|||
|
bbs.MaxY = (int)(bbs.MaxY * mde.Scale.Y);
|
|||
|
bbs.MinY = (int)(bbs.MinY * mde.Scale.Y);
|
|||
|
bbs.MaxZ = (int)(bbs.MaxZ * mde.Scale.Z);
|
|||
|
bbs.MinZ = (int)(bbs.MinZ * mde.Scale.Z);
|
|||
|
|
|||
|
//calculate model radius
|
|||
|
mde.Model.Radius = Math.Max(Math.Max(Math.Abs(bbs.MinY), Math.Abs(bbs.MaxY)), Math.Max(Math.Abs(bbs.MinX), Math.Abs(bbs.MaxX)));
|
|||
|
mde.Model.BBox = bbs;
|
|||
|
}
|
|||
|
|
|||
|
private static Texture GetTexture(List<DataReader> containers, string texturename)
|
|||
|
{
|
|||
|
Texture t = null;
|
|||
|
string[] extensions = new string[ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length + 1];
|
|||
|
|
|||
|
Array.Copy(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, 0, extensions, 1, ModelData.SUPPORTED_TEXTURE_EXTENSIONS.Length);
|
|||
|
extensions[0] = "";
|
|||
|
|
|||
|
// Try to load the texture as defined by its path. GZDoom doesn't care about extensions
|
|||
|
if (t == null)
|
|||
|
{
|
|||
|
foreach (string extension in extensions)
|
|||
|
{
|
|||
|
string name = Path.ChangeExtension(texturename, null) + extension;
|
|||
|
name = name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|||
|
|
|||
|
t = LoadTexture(containers, name);
|
|||
|
|
|||
|
if (t != null)
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Try to use an already defined texture. Again, just try out all extensions
|
|||
|
foreach (string extension in extensions)
|
|||
|
{
|
|||
|
string name = Path.ChangeExtension(texturename, null) + extension;
|
|||
|
|
|||
|
if (General.Map.Data.GetTextureExists(name))
|
|||
|
{
|
|||
|
ImageData image = General.Map.Data.GetTextureImage(name);
|
|||
|
image.LoadImageNow(false);
|
|||
|
|
|||
|
t = image.Texture;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// GZDoom can also ignore the path completely (because why not), so let's see if there's a texture with
|
|||
|
// just the skin name
|
|||
|
if (t == null)
|
|||
|
{
|
|||
|
string name = Path.ChangeExtension(Path.GetFileName(texturename), null);
|
|||
|
|
|||
|
if (General.Map.Data.GetTextureExists(name))
|
|||
|
{
|
|||
|
ImageData image = General.Map.Data.GetTextureImage(name);
|
|||
|
image.LoadImageNow(false);
|
|||
|
|
|||
|
t = image.Texture;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Or maybe it's a sprite
|
|||
|
if (t == null)
|
|||
|
{
|
|||
|
string name = Path.ChangeExtension(texturename, null);
|
|||
|
|
|||
|
if (General.Map.Data.GetSpriteExists(name))
|
|||
|
{
|
|||
|
ImageData image = General.Map.Data.GetSpriteImage(name);
|
|||
|
image.LoadImageNow(false);
|
|||
|
|
|||
|
t = image.Texture;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return t;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region ================== Utility
|
|||
|
|
|||
|
protected static MemoryStream LoadFile(List<DataReader> containers, string path, bool isModel)
|
|||
|
{
|
|||
|
foreach (DataReader dr in containers)
|
|||
|
{
|
|||
|
if (isModel && dr is WADReader) continue; //models cannot be stored in WADs
|
|||
|
|
|||
|
//load file
|
|||
|
if (dr.FileExists(path)) return dr.LoadFile(path);
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
protected static Texture LoadTexture(List<DataReader> containers, string path)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(path)) return null;
|
|||
|
|
|||
|
MemoryStream ms = LoadFile(containers, path, true);
|
|||
|
if (ms == null) return null;
|
|||
|
|
|||
|
Texture texture = null;
|
|||
|
|
|||
|
//create texture
|
|||
|
Bitmap bitmap = ImageDataFormat.TryLoadImage(ms);
|
|||
|
if (bitmap != null)
|
|||
|
{
|
|||
|
texture = new Texture(General.Map.Graphics, bitmap);
|
|||
|
}
|
|||
|
|
|||
|
return texture;
|
|||
|
}
|
|||
|
|
|||
|
protected static void CreateMesh(ref ModelLoadResult result, List<WorldVertex> verts, List<int> indices)
|
|||
|
{
|
|||
|
//create mesh
|
|||
|
Mesh mesh = new Mesh(General.Map.Graphics, verts.ToArray(), indices.ToArray());
|
|||
|
|
|||
|
//store in result
|
|||
|
result.Meshes.Add(mesh);
|
|||
|
}
|
|||
|
|
|||
|
protected static string ReadString(BinaryReader br, int len)
|
|||
|
{
|
|||
|
string result = string.Empty;
|
|||
|
int i;
|
|||
|
|
|||
|
for (i = 0; i < len; ++i)
|
|||
|
{
|
|||
|
var c = br.ReadChar();
|
|||
|
if (c == '\0')
|
|||
|
{
|
|||
|
++i;
|
|||
|
break;
|
|||
|
}
|
|||
|
result += c;
|
|||
|
}
|
|||
|
|
|||
|
for (; i < len; ++i) br.ReadChar();
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|