From 3f93525ebcf57ef0de7e10d342e6f99131921cea Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Mon, 18 Jul 2016 12:05:19 +0000
Subject: [PATCH] Added support for SurfaceSkin MODELDEF property. Changed,
 Visual mode: increased maximum rendreable dynamic lights count to 64.

---
 Source/Core/Data/DataManager.cs               |  2 +-
 Source/Core/GZBuilder/Data/ModelData.cs       |  6 +-
 Source/Core/GZBuilder/md3/ModelReader.cs      | 48 +++++++---
 .../Core/Windows/PreferencesForm.Designer.cs  | 19 ++--
 Source/Core/Windows/PreferencesForm.cs        |  6 +-
 Source/Core/ZDoom/ModeldefParser.cs           |  5 +-
 Source/Core/ZDoom/ModeldefStructure.cs        | 96 +++++++++++++++++--
 7 files changed, 143 insertions(+), 39 deletions(-)

diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index 3255a2612..1e5ca5356 100644
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -3210,7 +3210,7 @@ namespace CodeImp.DoomBuilder.Data
 			// Load the skysphere model...
 			BoundingBoxSizes bbs = new BoundingBoxSizes();
 			Stream modeldata = General.ThisAssembly.GetManifestResourceStream("CodeImp.DoomBuilder.Resources.SkySphere.md3");
-			ModelReader.MD3LoadResult meshes = ModelReader.ReadMD3Model(ref bbs, true, modeldata, device, 0);
+			ModelReader.MD3LoadResult meshes = ModelReader.ReadMD3Model(ref bbs, new Dictionary<int, string>(), modeldata, device, 0);
 			if(meshes.Meshes.Count != 3) throw new Exception("Skybox creation failed: " 
 				+ (string.IsNullOrEmpty(meshes.Errors) ? "skybox model must contain 3 surfaces" : meshes.Errors));
 
diff --git a/Source/Core/GZBuilder/Data/ModelData.cs b/Source/Core/GZBuilder/Data/ModelData.cs
index 0c5dcc39a..b139f10e0 100644
--- a/Source/Core/GZBuilder/Data/ModelData.cs
+++ b/Source/Core/GZBuilder/Data/ModelData.cs
@@ -30,7 +30,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 		#region ================== Properties
 
 		internal List<string> ModelNames;
-		internal List<string> TextureNames;
+		internal List<string> SkinNames;
+		internal List<Dictionary<int, string>> SurfaceSkinNames;
 		internal List<string> FrameNames;
 		internal List<int> FrameIndices;
 
@@ -62,7 +63,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 		internal ModelData() 
 		{
 			ModelNames = new List<string>();
-			TextureNames = new List<string>();
+			SkinNames = new List<string>();
+			SurfaceSkinNames = new List<Dictionary<int, string>>();
 			FrameNames = new List<string>();
 			FrameIndices = new List<int>();
 			transform = Matrix.Identity;
diff --git a/Source/Core/GZBuilder/md3/ModelReader.cs b/Source/Core/GZBuilder/md3/ModelReader.cs
index 5d1799c86..fb2c77148 100644
--- a/Source/Core/GZBuilder/md3/ModelReader.cs
+++ b/Source/Core/GZBuilder/md3/ModelReader.cs
@@ -77,7 +77,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 			}
 
 			//clear unneeded data
-			mde.TextureNames = null;
+			mde.SkinNames = null;
 			mde.ModelNames = null;
 
 			if(mde.Model.Meshes == null || mde.Model.Meshes.Count == 0) 
@@ -95,10 +95,15 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 			//load models and textures
 			for(int i = 0; i < mde.ModelNames.Count; i++) 
 			{
-				//need to use model skins?
-				bool useSkins = string.IsNullOrEmpty(mde.TextureNames[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
+				// Load mesh
 				MemoryStream ms = LoadFile(containers, mde.ModelNames[i], true);
 				if(ms == null) 
 				{
@@ -115,7 +120,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 							General.ErrorLogger.Add(ErrorType.Error, "Error while loading \"" + mde.ModelNames[i] + "\": frame names are not supported for MD3 models!");
 							continue;
 						}
-						result = ReadMD3Model(ref bbs, useSkins, ms, device, mde.FrameIndices[i]);
+						result = ReadMD3Model(ref bbs, skins, ms, device, mde.FrameIndices[i]);
 						break;
 					case ".md2":
 						result = ReadMD2Model(ref bbs, ms, device, mde.FrameIndices[i], mde.FrameNames[i]);
@@ -145,7 +150,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 					List<string> errors = new List<string>();
 
 					// Texture not defined in MODELDEF?
-					if(useSkins) 
+					if(skins != null) 
 					{
 						//try to use model's own skins 
 						for(int m = 0; m < result.Meshes.Count; m++) 
@@ -169,7 +174,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 
 							//relative path?
 							if(path.IndexOf(Path.DirectorySeparatorChar) == -1)
-								path = Path.Combine(Path.GetDirectoryName(mde.ModelNames[useSkins ? i : m]), path);
+								path = Path.Combine(Path.GetDirectoryName(mde.ModelNames[i]), path);
 
 							Texture t = LoadTexture(containers, path, device);
 
@@ -186,11 +191,11 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 					//Try to use texture loaded from MODELDEFS
 					else
 					{
-						Texture t = LoadTexture(containers, mde.TextureNames[i], device);
+						Texture t = LoadTexture(containers, mde.SkinNames[i], device);
 						if(t == null) 
 						{
 							mde.Model.Textures.Add(General.Map.Data.UnknownTexture3D.Texture);
-							errors.Add("unable to load texture \"" + mde.TextureNames[i] + "\"");
+							errors.Add("unable to load texture \"" + mde.SkinNames[i] + "\"");
 						} 
 						else 
 						{
@@ -208,7 +213,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 			}
 
 			//clear unneeded data
-			mde.TextureNames = null;
+			mde.SkinNames = null;
 			mde.ModelNames = null;
 
 			if(mde.Model.Meshes == null || mde.Model.Meshes.Count == 0) 
@@ -231,7 +236,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 
 		#region ================== MD3
 
-		internal static MD3LoadResult ReadMD3Model(ref BoundingBoxSizes bbs, bool useSkins, Stream s, Device device, int frame) 
+		internal static MD3LoadResult ReadMD3Model(ref BoundingBoxSizes bbs, Dictionary<int, string> skins, Stream s, Device device, int frame) 
 		{
 			long start = s.Position;
 			MD3LoadResult result = new MD3LoadResult();
@@ -265,6 +270,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 				Dictionary<string, List<List<int>>> polyIndecesListsPerTexture = new Dictionary<string, List<List<int>>>(StringComparer.Ordinal);
 				Dictionary<string, List<WorldVertex>> vertListsPerTexture = new Dictionary<string, List<WorldVertex>>(StringComparer.Ordinal);
 				Dictionary<string, List<int>> vertexOffsets = new Dictionary<string, List<int>>(StringComparer.Ordinal);
+				bool useskins = false;
 
 				for(int c = 0; c < numSurfaces; c++) 
 				{
@@ -277,8 +283,22 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 						return result;
 					}
 
-					if(useSkins) 
+					// Pick a skin to use
+					if(skins == null)
+					{
+						// skins is null when Skin MODELDEF property is set
+						skin = string.Empty;
+					}
+					else if(skins.ContainsKey(c))
+					{
+						// Overrtide surface skin with SurfaceSkin MODELDEF property
+						skin = skins[c];
+					}
+
+					if(!string.IsNullOrEmpty(skin))
 					{
+						useskins = true;
+						
 						if(polyIndecesListsPerTexture.ContainsKey(skin)) 
 						{
 							polyIndecesListsPerTexture[skin].Add(polyIndecesList);
@@ -298,8 +318,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 					}
 				}
 
-				if(!useSkins) 
-				{ 
+				if(!useskins) 
+				{
 					//create mesh
 					CreateMesh(device, ref result, vertList, polyIndecesList);
 					result.Skins.Add("");
diff --git a/Source/Core/Windows/PreferencesForm.Designer.cs b/Source/Core/Windows/PreferencesForm.Designer.cs
index 99dd8480e..5b528ceef 100644
--- a/Source/Core/Windows/PreferencesForm.Designer.cs
+++ b/Source/Core/Windows/PreferencesForm.Designer.cs
@@ -602,7 +602,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// label1
 			// 
 			label1.AutoSize = true;
-			label1.Location = new System.Drawing.Point(41, 171);
+			label1.Location = new System.Drawing.Point(43, 171);
 			label1.Name = "label1";
 			label1.Size = new System.Drawing.Size(145, 13);
 			label1.TabIndex = 20;
@@ -612,11 +612,11 @@ namespace CodeImp.DoomBuilder.Windows
 			// label18
 			// 
 			label18.AutoSize = true;
-			label18.Location = new System.Drawing.Point(41, 208);
+			label18.Location = new System.Drawing.Point(80, 208);
 			label18.Name = "label18";
-			label18.Size = new System.Drawing.Size(147, 13);
+			label18.Size = new System.Drawing.Size(108, 13);
 			label18.TabIndex = 25;
-			label18.Text = "Max. dynamic lights to render:";
+			label18.Text = "Dynamic lights count:";
 			label18.TextAlign = System.Drawing.ContentAlignment.TopRight;
 			this.toolTip1.SetToolTip(label18, "Controls how many dynamic lights could be \r\nrendered simultaneously in Visual mod" +
 					"e ");
@@ -654,7 +654,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// label29
 			// 
 			label29.AutoSize = true;
-			label29.Location = new System.Drawing.Point(90, 356);
+			label29.Location = new System.Drawing.Point(96, 356);
 			label29.Name = "label29";
 			label29.Size = new System.Drawing.Size(93, 13);
 			label29.TabIndex = 38;
@@ -1704,7 +1704,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// label32
 			// 
 			this.label32.AutoSize = true;
-			this.label32.Location = new System.Drawing.Point(44, 134);
+			this.label32.Location = new System.Drawing.Point(49, 134);
 			this.label32.Name = "label32";
 			this.label32.Size = new System.Drawing.Size(139, 13);
 			this.label32.TabIndex = 44;
@@ -1724,7 +1724,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// label30
 			// 
 			this.label30.AutoSize = true;
-			this.label30.Location = new System.Drawing.Point(16, 97);
+			this.label30.Location = new System.Drawing.Point(15, 97);
 			this.label30.Name = "label30";
 			this.label30.Size = new System.Drawing.Size(173, 13);
 			this.label30.TabIndex = 41;
@@ -1845,14 +1845,13 @@ namespace CodeImp.DoomBuilder.Windows
 			// tbDynLightCount
 			// 
 			this.tbDynLightCount.BackColor = System.Drawing.SystemColors.Window;
-			this.tbDynLightCount.LargeChange = 3;
+			this.tbDynLightCount.LargeChange = 1;
 			this.tbDynLightCount.Location = new System.Drawing.Point(199, 197);
-			this.tbDynLightCount.Maximum = 32;
+			this.tbDynLightCount.Maximum = 8;
 			this.tbDynLightCount.Minimum = 1;
 			this.tbDynLightCount.Name = "tbDynLightCount";
 			this.tbDynLightCount.Size = new System.Drawing.Size(154, 45);
 			this.tbDynLightCount.TabIndex = 5;
-			this.tbDynLightCount.TickFrequency = 4;
 			this.tbDynLightCount.TickStyle = System.Windows.Forms.TickStyle.TopLeft;
 			this.tbDynLightCount.Value = 1;
 			this.tbDynLightCount.ValueChanged += new System.EventHandler(this.tbDynLightCount_ValueChanged);
diff --git a/Source/Core/Windows/PreferencesForm.cs b/Source/Core/Windows/PreferencesForm.cs
index 08a79a0cc..c67bd62b3 100644
--- a/Source/Core/Windows/PreferencesForm.cs
+++ b/Source/Core/Windows/PreferencesForm.cs
@@ -97,7 +97,7 @@ namespace CodeImp.DoomBuilder.Windows
 			checkforupdates.Checked = General.Settings.CheckForUpdates;
 			toolbar_gzdoom.Checked = General.Settings.GZToolbarGZDoom;
 			cbSynchCameras.Checked = General.Settings.GZSynchCameras;
-			tbDynLightCount.Value = General.Clamp(General.Settings.GZMaxDynamicLights, tbDynLightCount.Minimum, tbDynLightCount.Maximum);
+			tbDynLightCount.Value = General.Clamp(General.Settings.GZMaxDynamicLights / 8, tbDynLightCount.Minimum, tbDynLightCount.Maximum);
 			labelDynLightCount.Text = General.Settings.GZMaxDynamicLights.ToString();
 			tbDynLightSize.Value = General.Clamp((int)(General.Settings.GZDynamicLightRadius * 10), tbDynLightSize.Minimum, tbDynLightSize.Maximum);
 			labelDynLightSize.Text = General.Settings.GZDynamicLightRadius.ToString();
@@ -409,7 +409,7 @@ namespace CodeImp.DoomBuilder.Windows
 
 			//mxd
 			General.Settings.GZSynchCameras = cbSynchCameras.Checked;
-			General.Settings.GZMaxDynamicLights = tbDynLightCount.Value;
+			General.Settings.GZMaxDynamicLights = tbDynLightCount.Value * 8;
 			General.Settings.GZDynamicLightRadius = (tbDynLightSize.Value / 10.0f);
 			General.Settings.GZDynamicLightIntensity = (tbDynLightIntensity.Value / 10.0f);
 			General.Settings.FilterAnisotropy = D3DDevice.AF_STEPS[anisotropicfiltering.Value];
@@ -1006,7 +1006,7 @@ namespace CodeImp.DoomBuilder.Windows
 		//mxd
 		private void tbDynLightCount_ValueChanged(object sender, EventArgs e) 
 		{
-			labelDynLightCount.Text = tbDynLightCount.Value.ToString();
+			labelDynLightCount.Text = (tbDynLightCount.Value * 8).ToString();
 		}
 
 		//mxd
diff --git a/Source/Core/ZDoom/ModeldefParser.cs b/Source/Core/ZDoom/ModeldefParser.cs
index 62654eaa0..47cb8174f 100644
--- a/Source/Core/ZDoom/ModeldefParser.cs
+++ b/Source/Core/ZDoom/ModeldefParser.cs
@@ -118,9 +118,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 									}
 									
 									// Texture name will be empty when skin path is embedded in the model
-									string texturename = (!string.IsNullOrEmpty(mds.TextureNames[fs.ModelIndex]) ? mds.TextureNames[fs.ModelIndex].ToLowerInvariant() : string.Empty);
+									string skinname = (!string.IsNullOrEmpty(mds.SkinNames[fs.ModelIndex]) ? mds.SkinNames[fs.ModelIndex].ToLowerInvariant() : string.Empty);
 
-									md.TextureNames.Add(texturename);
+									md.SkinNames.Add(skinname);
+									md.SurfaceSkinNames.Add(mds.SurfaceSkinNames[fs.ModelIndex]); 
 									md.ModelNames.Add(mds.ModelNames[fs.ModelIndex].ToLowerInvariant());
 									md.FrameNames.Add(fs.FrameName);
 									md.FrameIndices.Add(fs.FrameIndex);
diff --git a/Source/Core/ZDoom/ModeldefStructure.cs b/Source/Core/ZDoom/ModeldefStructure.cs
index b4b0dcbb0..bad406029 100644
--- a/Source/Core/ZDoom/ModeldefStructure.cs
+++ b/Source/Core/ZDoom/ModeldefStructure.cs
@@ -33,7 +33,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Variables
 
-		private string[] texturenames;
+		private string[] skinnames;
+		private Dictionary<int, string>[] surfaceskinenames;
 		private string[] modelnames;
 		private string path;
 		private Vector3 scale;
@@ -51,7 +52,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Properties
 
-		public string[] TextureNames { get { return texturenames; } }
+		public string[] SkinNames { get { return skinnames; } }
+		public Dictionary<int, string>[] SurfaceSkinNames { get { return surfaceskinenames; } }
 		public string[] ModelNames { get { return modelnames; } }
 		public Vector3 Scale { get { return scale; } }
 		public Vector3 Offset { get { return offset; } }
@@ -70,10 +72,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		internal ModeldefStructure()
 		{
-			texturenames = new string[MAX_MODELS];
+			skinnames = new string[MAX_MODELS];
 			modelnames = new string[MAX_MODELS];
 			frames = new Dictionary<string, HashSet<FrameStructure>>(StringComparer.OrdinalIgnoreCase);
 			scale = new Vector3(1.0f, 1.0f, 1.0f);
+			surfaceskinenames = new Dictionary<int, string>[MAX_MODELS];
+			for(int i = 0; i < MAX_MODELS; i++)
+			{
+				surfaceskinenames[i] = new Dictionary<int, string>();
+			}
 		}
 
 		#endregion
@@ -194,7 +201,72 @@ namespace CodeImp.DoomBuilder.ZDoom
 						} 
 
 						// GZDoom allows skins with identical index, it uses the last one encountered
-						texturenames[skinindex] = Path.Combine(path, token);
+						skinnames[skinindex] = Path.Combine(path, token);
+						break;
+
+					// SurfaceSkin <int modelindex> <int surfaceindex> <string skinfile>
+					case "surfaceskin":
+						parser.SkipWhitespace(true);
+
+						// Model index
+						int modelindex = 0;
+						token = parser.ReadToken();
+						if(!parser.ReadSignedInt(token, ref modelindex))
+						{
+							// Not numeric!
+							parser.ReportError("Expected model index, but got \"" + token + "\"");
+							return false;
+						}
+
+						if(modelindex < 0 || modelindex >= MAX_MODELS)
+						{
+							// Out of bounds
+							parser.ReportError("Model index must be in [0.." + (MAX_MODELS - 1) + "] range");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+
+						// Surfaceindex index
+						int surfaceindex = 0;
+						token = parser.ReadToken();
+						if(!parser.ReadSignedInt(token, ref surfaceindex))
+						{
+							// Not numeric!
+							parser.ReportError("Expected surface index, but got \"" + token + "\"");
+							return false;
+						}
+
+						if(surfaceindex < 0)
+						{
+							// Out of bounds
+							parser.ReportError("Surface index must be positive integer");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+
+						// Skin path
+						token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
+						if(string.IsNullOrEmpty(token))
+						{
+							parser.ReportError("Expected skin path");
+							return false;
+						}
+
+						// Check invalid path chars
+						if(!parser.CheckInvalidPathChars(token)) return false;
+
+						// Check extension
+						string skinext = Path.GetExtension(token);
+						if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, skinext) == -1)
+						{
+							parser.ReportError("Image format \"" + skinext + "\" is not supported");
+							return false;
+						} 
+
+						// Store
+						surfaceskinenames[modelindex][surfaceindex] = Path.Combine(path, token);
 						break;
 
 					case "scale":
@@ -511,11 +583,21 @@ namespace CodeImp.DoomBuilder.ZDoom
 			}
 
 			// Check skin-model associations
-			for(int i = 0; i < texturenames.Length; i++)
+			for(int i = 0; i < skinnames.Length; i++)
+			{
+				if(!string.IsNullOrEmpty(skinnames[i]) && string.IsNullOrEmpty(modelnames[i]))
+				{
+					parser.ReportError("No model is defined for skin " + i + ":\"" + skinnames[i] + "\"");
+					return false;
+				}
+			}
+
+			// Check surfaceskin-model associations
+			for(int i = 0; i < surfaceskinenames.Length; i++)
 			{
-				if(!string.IsNullOrEmpty(texturenames[i]) && string.IsNullOrEmpty(modelnames[i]))
+				if(surfaceskinenames[i].Count > 0 && string.IsNullOrEmpty(modelnames[i]))
 				{
-					parser.ReportError("No model is defined for skin " + i + ":\"" + texturenames[i] + "\"");
+					parser.ReportError("No model is defined for surface skin " + i);
 					return false;
 				}
 			}
-- 
GitLab