UltimateZoneBuilder/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs

495 lines
17 KiB
C#
Raw Normal View History

#region ================== Namespaces
using System;
using System.IO;
using System.Globalization;
using SlimDX;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.Geometry;
#endregion
namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
{
internal sealed class ModeldefStructure
{
private const int MAX_MODELS = 4; //maximum models per modeldef entry, zero-based
internal ModelData Parse(ModeldefParser parser)
{
#region ================== Vars
string[] textureNames = new string[MAX_MODELS];
string[] modelNames = new string[MAX_MODELS];
string[] frameNames = new string[MAX_MODELS];
int[] frameIndices = new int[MAX_MODELS];
bool[] modelsUsed = new bool[MAX_MODELS];
string path = "";
Vector3 scale = new Vector3(1, 1, 1);
Vector3 offset = new Vector3();
float angleOffset = 0;
float pitchOffset = 0;
float rollOffset = 0;
bool inheritactorpitch = false;
bool inheritactorroll = false;
string token;
bool gotErrors = false;
bool allParsed = false;
#endregion
//read modeldef structure contents
while(!gotErrors && !allParsed && parser.SkipWhitespace(true))
{
token = parser.ReadToken();
if(!string.IsNullOrEmpty(token))
{
token = parser.StripTokenQuotes(token).ToLowerInvariant(); //ANYTHING can be quoted...
switch(token)
{
#region ================== Path
case "path":
parser.SkipWhitespace(true);
path = parser.StripTokenQuotes(parser.ReadToken()).Replace("/", "\\");
if(string.IsNullOrEmpty(path))
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected path to model, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== Model
case "model":
parser.SkipWhitespace(true);
//model index
int index;
token = parser.StripTokenQuotes(parser.ReadToken());
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected model index, but got '" + token + "'");
gotErrors = true;
break;
}
if(index >= MAX_MODELS)
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry!");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
//model path
token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
if(string.IsNullOrEmpty(token))
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected model name, but got '" + token + "'");
gotErrors = true;
}
else
{
//check extension
string fileExt = Path.GetExtension(token);
if(string.IsNullOrEmpty(fileExt))
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": model '" + token + "' won't be loaded. Models without extension are not supported by GZDoom.");
gotErrors = true;
break;
}
if(fileExt != ".md3" && fileExt != ".md2")
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": model '" + token + "' won't be loaded. Only MD2 and MD3 models are supported.");
gotErrors = true;
break;
}
2013-03-18 13:52:27 +00:00
//GZDoom allows models with identical modelIndex, it uses the last one encountered
modelNames[index] = Path.Combine(path, token);
}
break;
#endregion
#region ================== Skin
case "skin":
parser.SkipWhitespace(true);
//skin index
int skinIndex;
token = parser.StripTokenQuotes(parser.ReadToken());
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out skinIndex))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected skin index, but got '" + token + "'");
gotErrors = true;
break;
}
if(skinIndex >= MAX_MODELS)
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": GZDoom doesn't allow more than " + MAX_MODELS + " skins per MODELDEF entry!");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
//skin path
token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
if(string.IsNullOrEmpty(token))
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected skin name, but got '" + token + "'");
gotErrors = true;
}
else
{
//check extension
string ext = Path.GetExtension(token);
if(Array.IndexOf(TextureData.SUPPORTED_TEXTURE_EXTENSIONS, ext) == -1)
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": image format '" + ext + "' is not supported!");
textureNames[skinIndex] = TextureData.INVALID_TEXTURE;
}
else
{
//GZDoom allows skins with identical modelIndex, it uses the last one encountered
textureNames[skinIndex] = Path.Combine(path, token);
}
}
break;
#endregion
#region ================== Scale
case "scale":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref scale.Y))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Scale X value, but got '" + token + "'");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref scale.X))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Scale Y value, but got '" + token + "'");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref scale.Z))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Scale Z value, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== Offset
case "offset":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref offset.X))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Offset X value, but got '" + token + "'");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref offset.Y))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Offset Y value, but got '" + token + "'");
gotErrors = true;
break;
}
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected Offset Z value, but got '" + token + "'");
gotErrors = true;
2013-03-18 13:52:27 +00:00
}
break;
#endregion
#region ================== ZOffset
case "zoffset":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected ZOffset value, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== AngleOffset
case "angleoffset":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref angleOffset))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected AngleOffset value, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== PitchOffset
case "pitchoffset":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref pitchOffset))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected PitchOffset value, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== RollOffset
case "rolloffset":
parser.SkipWhitespace(true);
token = parser.StripTokenQuotes(parser.ReadToken());
if(!parser.ReadSignedFloat(token, ref rollOffset))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected RollOffset value, but got '" + token + "'");
gotErrors = true;
}
break;
#endregion
#region ================== InheritActorPitch
case "inheritactorpitch":
inheritactorpitch = true;
break;
#endregion
#region ================== InheritActorRoll
case "inheritactorroll":
inheritactorroll = true;
break;
#endregion
#region ================== Frame / FrameIndex
case "frameindex":
case "frame":
//parsed all required fields. if got more than one model - find which one(s) should be displayed
int len = modelNames.GetLength(0);
if(!gotErrors && len > 1)
{
string spriteLump = null;
string spriteFrame = null;
//step back
parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
//here we check which models are used in first encountered lump and frame
while(parser.SkipWhitespace(true))
{
token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
if(token == "frameindex" || token == "frame")
{
bool frameIndex = (token == "frameindex");
parser.SkipWhitespace(true);
//should be sprite lump
token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
if(string.IsNullOrEmpty(spriteLump))
{
spriteLump = token;
}
else if(spriteLump != token) //got another lump
{
for(int i = 0; i < modelsUsed.Length; i++)
{
if(!modelsUsed[i])
{
modelNames[i] = null;
textureNames[i] = null;
}
}
break;
}
parser.SkipWhitespace(true);
//should be sprite frame
token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
if(string.IsNullOrEmpty(spriteFrame))
{
spriteFrame = token;
}
else if(spriteFrame != token) //got another frame
{
for(int i = 0; i < modelsUsed.Length; i++)
{
if(!modelsUsed[i])
{
modelNames[i] = null;
textureNames[i] = null;
}
}
break;
}
parser.SkipWhitespace(true);
//should be model index
token = parser.StripTokenQuotes(parser.ReadToken());
int modelIndex;
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out modelIndex))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected model index, but got '" + token + "'");
gotErrors = true;
break;
}
if(modelIndex >= MAX_MODELS)
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry!");
gotErrors = true;
break;
}
if(modelNames[modelIndex] == null)
{
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": got model index, which doesn't correspond to any defined model!");
gotErrors = true;
break;
}
modelsUsed[modelIndex] = true;
parser.SkipWhitespace(true);
// Should be frame name or index
token = parser.StripTokenQuotes(parser.ReadToken());
if(frameIndex)
{
int frame = 0;
if(!parser.ReadSignedInt(token, ref frame))
{
// Not numeric!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected model frame index, but got '" + token + "'");
gotErrors = true;
break;
}
// Skip the model if frame index is -1
if(frame == -1) modelsUsed[modelIndex] = false;
else frameIndices[modelIndex] = frame;
}
else
{
if(string.IsNullOrEmpty(token))
{
// Missing!
General.ErrorLogger.Add(ErrorType.Error, "Error in " + parser.Source + " at line " + parser.GetCurrentLineNumber() + ": expected model frame name");
gotErrors = true;
break;
}
frameNames[modelIndex] = token.ToLowerInvariant();
}
}
else
{
//must be "}", step back
parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
break;
}
}
}
allParsed = true;
break;
#endregion
}
}
}
// Find closing brace, then quit
while(parser.SkipWhitespace(true))
{
token = parser.ReadToken();
if(string.IsNullOrEmpty(token) || token == "}") break;
}
// Bail out when got errors or no models are used
if(gotErrors || Array.IndexOf(modelsUsed, true) == -1) return null;
// Classname is set in ModeldefParser
ModelData mde = new ModelData();
mde.InheritActorPitch = inheritactorpitch;
mde.InheritActorRoll = inheritactorroll;
Visual mode: "Fit Textures" action can now fit textures across multiple selected surfaces. A number of times to repeat a texture can now be specified. Visual mode: removed "Fit Texture's Width" and "Fit Texture's Height" actions. Visual mode: "Auto-align texture offsets" actions were incorrectly aligning double-sided middle walls in some cases. Visual mode: "Auto-align texture offsets" actions now align non-wrapped double-sided middle walls to vertical offset closest to their initial vertical offset. Visual mode: middle parts of double-sided walls were ignored when Shift-selecting walls. Nodebuilders/Game configurations: GL nodes definitions were missing from game configurations. Nodebuilders/Game configurations: "~MAP" wildcard can now be a part of a lump name. Nodebuilders: GL nodes were not properly handled by the editor. Main Window: the window is now moved into the view when stored position is ouside of screen bounds. Classic and Visual modes: changing thing pitch was ignored in some cases. Visual mode: raising and lowering a thing with "+SPAWNCEILING" flag now works the same way as when raising/lowering a regular thing. Visual mode: using "Raise/Lower Floor/Ceiling to adjacent sector" actions on a thing with "+SPAWNCEILING" flag now works the same way as when using them on a regular thing. Rendering: even more fixes to MODELDEF and UDMF properties-related model rendering logic. Internal, ResourceListEditor: rewritten resource validation check in a more OOP-ish way. Configurations: fixed an infinite loop crash when a file was trying to include() itself. UDMF thing flags: added Skill 6-8 to the flags list (because there are thing filters for these). ZDoom_ACS.cfg: added definitions for SetTeleFog and SwapTeleFog. ZDoom_DECORATE.cfg: added definitions for A_SetTeleFog and A_SwapTeleFog. Updated ZDoom ACC. Updated documentation.
2014-12-22 21:36:49 +00:00
Matrix moffset = Matrix.Translation(offset.Y, -offset.X, offset.Z); // Things are complicated in GZDoom...
Matrix mrotation = Matrix.RotationY(-Angle2D.DegToRad(rollOffset)) * Matrix.RotationX(-Angle2D.DegToRad(pitchOffset)) * Matrix.RotationZ(Angle2D.DegToRad(angleOffset));
Visual mode: "Fit Textures" action can now fit textures across multiple selected surfaces. A number of times to repeat a texture can now be specified. Visual mode: removed "Fit Texture's Width" and "Fit Texture's Height" actions. Visual mode: "Auto-align texture offsets" actions were incorrectly aligning double-sided middle walls in some cases. Visual mode: "Auto-align texture offsets" actions now align non-wrapped double-sided middle walls to vertical offset closest to their initial vertical offset. Visual mode: middle parts of double-sided walls were ignored when Shift-selecting walls. Nodebuilders/Game configurations: GL nodes definitions were missing from game configurations. Nodebuilders/Game configurations: "~MAP" wildcard can now be a part of a lump name. Nodebuilders: GL nodes were not properly handled by the editor. Main Window: the window is now moved into the view when stored position is ouside of screen bounds. Classic and Visual modes: changing thing pitch was ignored in some cases. Visual mode: raising and lowering a thing with "+SPAWNCEILING" flag now works the same way as when raising/lowering a regular thing. Visual mode: using "Raise/Lower Floor/Ceiling to adjacent sector" actions on a thing with "+SPAWNCEILING" flag now works the same way as when using them on a regular thing. Rendering: even more fixes to MODELDEF and UDMF properties-related model rendering logic. Internal, ResourceListEditor: rewritten resource validation check in a more OOP-ish way. Configurations: fixed an infinite loop crash when a file was trying to include() itself. UDMF thing flags: added Skill 6-8 to the flags list (because there are thing filters for these). ZDoom_ACS.cfg: added definitions for SetTeleFog and SwapTeleFog. ZDoom_DECORATE.cfg: added definitions for A_SetTeleFog and A_SwapTeleFog. Updated ZDoom ACC. Updated documentation.
2014-12-22 21:36:49 +00:00
mde.SetTransform(mrotation, moffset, scale);
for(int i = 0; i < modelNames.Length; i++)
{
if(!string.IsNullOrEmpty(modelNames[i]) && modelsUsed[i])
{
mde.TextureNames.Add(string.IsNullOrEmpty(textureNames[i]) ? string.Empty : textureNames[i].ToLowerInvariant());
mde.ModelNames.Add(modelNames[i].ToLowerInvariant());
mde.FrameNames.Add(frameNames[i]);
mde.FrameIndices.Add(frameIndices[i]);
}
}
return (mde.ModelNames.Count > 0 ? mde : null);
}
}
}