From ec9c3a71f96138d17c9e3cbb03faf7dc29cf14f0 Mon Sep 17 00:00:00 2001 From: MaxED Date: Mon, 11 Jul 2016 22:13:43 +0000 Subject: [PATCH] Added: preview sprites are now generated from voxels. Those are used as previews and when model rendering is disabled. Fixed: voxel support logic was outdated, which in some cases resulted in voxels not being loaded and in some weird behaviour when trying to change pitch/roll of associated things. Updated sprites lookup logic. Now it correctly handles sprites named like NNNNA0B0. Fixed: in some cases DECORATE parser was unable to correctly detect sprite name & frame(s) block. Updated ZDoom_ACS.cfg. Updated ZDoom_DECORATE.cfg. --- Build/Configurations/Includes/ZDoom_misc.cfg | 4 +- Build/Scripting/ZDoom_ACS.cfg | 2 +- Build/Scripting/ZDoom_DECORATE.cfg | 16 +- Source/Core/Builder.csproj | 3 +- Source/Core/Config/ThingTypeInfo.cs | 133 +++++-- Source/Core/Data/DataManager.cs | 209 ++++++----- Source/Core/Data/DataReader.cs | 8 +- Source/Core/Data/DirectoryReader.cs | 5 +- Source/Core/Data/PK3Reader.cs | 5 +- Source/Core/Data/PK3StructuredReader.cs | 27 +- Source/Core/Data/SpriteImage.cs | 10 +- Source/Core/Data/VoxelImage.cs | 325 ++++++++++++++++++ Source/Core/Data/WADReader.cs | 39 ++- Source/Core/GZBuilder/Data/ModelData.cs | 3 +- Source/Core/GZBuilder/md3/ModelReader.cs | 3 +- Source/Core/Map/Thing.cs | 18 +- .../{Config => Rendering}/RenderModeEnums.cs | 3 +- Source/Core/Rendering/Renderer2D.cs | 8 +- Source/Core/Rendering/Renderer3D.cs | 14 +- Source/Core/VisualModes/VisualThing.cs | 5 +- Source/Core/ZDoom/ActorStructure.cs | 90 +---- Source/Core/ZDoom/StateStructure.cs | 53 ++- Source/Core/ZDoom/VoxeldefParser.cs | 7 +- .../VisualModes/BaseVisualGeometrySidedef.cs | 5 +- .../VisualModes/BaseVisualThing.cs | 2 +- 25 files changed, 720 insertions(+), 277 deletions(-) create mode 100644 Source/Core/Data/VoxelImage.cs rename Source/Core/{Config => Rendering}/RenderModeEnums.cs (75%) diff --git a/Build/Configurations/Includes/ZDoom_misc.cfg b/Build/Configurations/Includes/ZDoom_misc.cfg index e686f442..67629584 100644 --- a/Build/Configurations/Includes/ZDoom_misc.cfg +++ b/Build/Configurations/Includes/ZDoom_misc.cfg @@ -1426,14 +1426,14 @@ enums_strife 1 = "Base Key (Front)"; 2 = "Governor's Key"; 3 = "Travel Passcard"; - 4 = "Blue ID Badge"; + 4 = "ID Badge"; 5 = "Prison Key"; 6 = "Severed Hand"; 7 = "Power Key 1"; 8 = "Power Key 2"; 9 = "Power Key 3"; 10 = "Gold Key"; - 11 = "Gold ID Badge"; + 11 = "ID Card"; 12 = "Silver Key"; 13 = "Oracle Key"; 14 = "Military ID"; diff --git a/Build/Scripting/ZDoom_ACS.cfg b/Build/Scripting/ZDoom_ACS.cfg index 5b008f76..d808289b 100644 --- a/Build/Scripting/ZDoom_ACS.cfg +++ b/Build/Scripting/ZDoom_ACS.cfg @@ -310,7 +310,7 @@ keywords PolyWait = "void PolyWait(int polyid)"; Print = "void Print(type:expression)\nPrint will print something to the screen.\nPrint will only display for the activator of the script\nFor printing to all players, use PrintBold."; PrintBold = "void PrintBold(type:expression)\nThis is exactly the same as Print, except all players will see the printed text\non the screen instead of just the activator of the script."; - QuakeEx = "bool QuakeEx(int tid, int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad, str sound[, int flags = 0[, float mulwavex = 1.0[, float mulwavey = 1.0[, float mulwavez = 1.0[, int falloff = 0[, int highpoint = 0]]]]]])"; + QuakeEx = "bool QuakeEx(int tid, int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad, str sound[, int flags = 0[, float mulwavex = 1.0[, float mulwavey = 1.0[, float mulwavez = 1.0[, int falloff = 0[, int highpoint = 0[, float rollintensity = 0.0[, float rollwave = 0.0]]]]]]]])"; Radius_Quake = "Radius_Quake(intensity, duration, damrad, tremrad, tid)"; Radius_Quake2 = "void Radius_Quake2(int tid, int intensity, int duration, int damrad, int tremrad, str sound)"; Random = "int Random(int min, int max)"; diff --git a/Build/Scripting/ZDoom_DECORATE.cfg b/Build/Scripting/ZDoom_DECORATE.cfg index 23efb9d1..afc4c442 100644 --- a/Build/Scripting/ZDoom_DECORATE.cfg +++ b/Build/Scripting/ZDoom_DECORATE.cfg @@ -128,6 +128,7 @@ keywords A_BulletAttack = "A_BulletAttack"; A_MonsterRail = "A_MonsterRail"; A_Explode = "A_Explode[(int explosiondamage = 128[, int explosionradius = 128[, int flags = XF_HURTSOURCE[, bool alert = false[, int fulldamageradius = 0[, int nails = 0[, int naildamage = 10[, str pufftype = \"BulletPuff\"]]]]]]])]"; + A_RadiusDamageSelf = "[(int damage = 128[, float distance = 128.0[, int flags = 0[, str flashtype = \"None\"]]])]\nflags: RDSF flags"; A_RadiusThrust = "A_RadiusThrust(int force, int distance[, int flags[, int fullthrustdistance]])"; A_Detonate = "A_Detonate"; A_ThrowGrenade = "bool A_ThrowGrenade(str spawntype[, float spawnheight[, float throwspeed_horz[, float throwspeed_vert[, bool useammo]]]])"; @@ -176,7 +177,7 @@ keywords A_SpawnDebris = "A_SpawnDebris(str type[, bool translation = false[, float horizontal_vel = 1.0[, float vertical_vel = 1.0]]])"; A_SpawnItem = "bool A_SpawnItem(str type, int distance, float zpos, bool useammo, bool translation)"; A_SpawnItemEx = "bool A_SpawnItemEx(str type[, float xoffset = 0.0[, float yoffset = 0.0[, float zoffset = 0.0[, float xvelocity = 0.0[, float yvelocity = 0.0[, float zvelocity = 0.0[, float angle = 0.0[, int flags = 0[, int skipchance = 0[, int tid = 0]]]]]]]]]])"; - A_SpawnParticle = "A_SpawnParticle(color color[, int flags = 0[, int lifetime = 35[, int size = 1[, float angle = 0.0[, float xoff = 0.0[, float yoff = 0.0[, float zoff = 0.0[, float velx = 0.0[, float vely = 0.0[, float velz = 0.0[, float accelx = 0.0[, float accely = 0.0[, float accelz = 0.0[, float startalpha = 1.0[, float fadestep = -1.0]]]]]]]]]]]]]]])"; + A_SpawnParticle = "A_SpawnParticle(color color[, int flags = 0[, int lifetime = 35[, float size = 1.0[, float angle = 0.0[, float xoff = 0.0[, float yoff = 0.0[, float zoff = 0.0[, float velx = 0.0[, float vely = 0.0[, float velz = 0.0[, float accelx = 0.0[, float accely = 0.0[, float accelz = 0.0[, float startalpha = 1.0[, float fadestep = -1.0[, float sizestep = 0.0]]]]]]]]]]]]]]]])"; //State jumps A_CheckBlock = "state A_CheckBlock(str block[, int flags = 0[, int pointer = AAPTR_TARGET[, float xoff = 0.0[, float yoff = 0.0[, float zoff = 0.0[, float angle = 0.0]]]]]])"; A_CheckCeiling = "state A_CheckCeiling(str state)\nstate A_CheckCeiling(int offset)"; @@ -307,9 +308,9 @@ keywords A_Light1 = "A_Light1"; A_Light2 = "A_Light2"; A_LightInverse = "A_LightInverse"; - A_Overlay = "bool A_Overlay(int layer[, state start[, bool nooverride]])"; + A_Overlay = "bool A_Overlay(int layer[, state start = \"\"[, bool nooverride = false]])"; A_OverlayFlags = "A_OverlayFlags(int layer, int flags, bool set)\nflags: PSPF flags."; - A_OverlayOffset = "A_OverlayOffset[(int layer = 0[, float x = 0.0f[, float y = 32.0f[, int flags = 0]]]])]\nflags: WOF flags."; + A_OverlayOffset = "A_OverlayOffset[(int layer = 0[, float x = 0.0[, float y = 32.0[, int flags = 0]]]])]\nflags: WOF flags."; A_Recoil = "A_Recoil(float force)"; A_ZoomFactor = "A_ZoomFactor[(float zoom = 1.0[, int flags = 0])]\nflags: ZOOM flags."; A_SetCrosshair = "A_SetCrosshair(int number)"; @@ -378,7 +379,7 @@ keywords A_SkelWhoosh = "A_SkelWhoosh"; A_StartFire = "A_StartFire"; A_FireCrackle = "A_FireCrackle"; - A_BFGSpray = "A_BFGSpray[(str flashtype = \"BFGExtra\"[, int numrays = 40[, int damagecnt = 15[, float angle = 90.0[, float distance = 1024.0[, float vrange = 32.0[, int explicit_damage = 0]]]]]])]"; + A_BFGSpray = "A_BFGSpray[(str flashtype = \"BFGExtra\"[, int numrays = 40[, int damagecnt = 15[, float angle = 90.0[, float distance = 1024.0[, float vrange = 32.0[, int explicit_damage = 0[, int flags = 0]]]]]]])]\nflags: BFGR flags."; A_BarrelDestroy = "A_BarrelDestroy"; //Miscellaneous functions not listed in the "Action functions" wiki article A_Bang4Cloud = "A_Bang4Cloud"; @@ -1368,5 +1369,10 @@ constants PSPF_ADDWEAPON; PSPF_ADDBOB; PSPF_POWDOUBLE; - PSPF_CVARFAST; + PSPF_CVARFAST; +//A_BFGSpray flags + BFGF_MISSILEORIGIN; + BFGF_HURTSOURCE; +//A_RadiusDamageSelf flags + RDSF_BFGDAMAGE; } diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index 23db2d52..6012955c 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -705,7 +705,7 @@ - + @@ -825,6 +825,7 @@ + diff --git a/Source/Core/Config/ThingTypeInfo.cs b/Source/Core/Config/ThingTypeInfo.cs index 1a313903..6bba25ad 100644 --- a/Source/Core/Config/ThingTypeInfo.cs +++ b/Source/Core/Config/ThingTypeInfo.cs @@ -24,6 +24,7 @@ using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.GZBuilder.Data; using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Map; +using CodeImp.DoomBuilder.Rendering; using CodeImp.DoomBuilder.ZDoom; #endregion @@ -459,9 +460,8 @@ namespace CodeImp.DoomBuilder.Config // Set sprite StateStructure.FrameInfo info = actor.FindSuitableSprite(); //mxd - string suitablesprite = (locksprite ? string.Empty : info.Sprite); //mxd - if(!string.IsNullOrEmpty(suitablesprite)) - sprite = suitablesprite; + if(!locksprite && !string.IsNullOrEmpty(info.Sprite)) //mxd. Added locksprite property + sprite = info.Sprite; else if(string.IsNullOrEmpty(sprite))//mxd sprite = DataManager.INTERNAL_PREFIX + "unknownthing"; @@ -548,42 +548,121 @@ namespace CodeImp.DoomBuilder.Config if(blocking > THING_BLOCKING_NONE) errorcheck = THING_ERROR_INSIDE_STUCK; } - //mxd. This tries to find all possible sprite rotations - internal void SetupSpriteFrame() + //mxd. This tries to find all possible sprite rotations. Returns true when voxel substitute exists + internal bool SetupSpriteFrame(HashSet allspritenames, HashSet allvoxelnames) { - // Empty or internal sprites don't have rotations - if(string.IsNullOrEmpty(sprite) || sprite.StartsWith(DataManager.INTERNAL_PREFIX)) return; + // Empty, invalid or internal sprites don't have rotations + // Info: we can have either partial 5-char sprite name from DECORATE parser, + // or fully defined 6/8-char sprite name defined in Game configuration or by $Sprite property + if(string.IsNullOrEmpty(sprite) || sprite.StartsWith(DataManager.INTERNAL_PREFIX) + || (sprite.Length != 5 && sprite.Length != 6 && sprite.Length != 8)) return false; - // Skip sprites with strange names - if(sprite.Length != 6 && sprite.Length != 8) return; + string sourcename = sprite.Substring(0, 4); + char sourceframe = sprite[4]; + + // First try voxels + if(allvoxelnames.Count > 0) + { + // Find a voxel, which matches sourcename + HashSet voxelnames = new HashSet(); + foreach(string s in allvoxelnames) + { + if(s.StartsWith(sourcename)) voxelnames.Add(s); + } + + // Find a voxel, which matches baseframe + // Valid voxel can be either 4-char (POSS), 5-char (POSSA) or 6-char (POSSA0) + string newsprite = string.Empty; + + // Check 6-char voxels... + foreach(string v in voxelnames) + { + if(v.Length == 6 && v.StartsWith(sourcename + sourceframe) && WADReader.IsValidSpriteName(v)) + { + newsprite = v; + break; + } + } + + // Check 5-char voxels... + if(voxelnames.Contains(sourcename + sourceframe)) newsprite = sourcename + sourceframe; + + // Check 4-char voxels... + if(voxelnames.Contains(sourcename)) newsprite = sourcename; + + // Voxel found? + if(!string.IsNullOrEmpty(newsprite)) + { + // Assign new sprite + sprite = newsprite; + + // Recreate sprite frame + spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true) } }; + + // Substitute voxel found + return true; + } + } + + // Then try sprites + // Find a sprite, which matches sourcename + string sourcesprite = string.Empty; + HashSet spritenames = new HashSet(); + foreach(string s in allspritenames) + { + if(s.StartsWith(sourcename)) spritenames.Add(s); + } + + // Find a sprite, which matches baseframe + foreach(string s in spritenames) + { + if(s[4] == sourceframe || (s.Length == 8 && s[6] == sourceframe)) + { + sourcesprite = s; + break; + } + } + + // Abort if no sprite was found + if(string.IsNullOrEmpty(sourcesprite)) return false; // Get sprite angle - string anglestr = sprite.Substring(5, 1); + string anglestr = sourcesprite.Substring(5, 1); int sourceangle; if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out sourceangle)) { - General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + sprite + "\""); - return; + General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + sourcesprite + "\""); + return false; } if(sourceangle < 0 || sourceangle > 8) { - General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + sprite + "\". Sprite angle must be in [0..8] range"); - return; + General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + sourcesprite + "\". Sprite angle must be in [0..8] range"); + return false; } - // No rotations? - if(sourceangle == 0) return; + // No rotations? Then spriteframe is already setup + if(sourceangle == 0) + { + // Sprite name still incomplete? + if(sprite.Length < 6) + { + sprite = sourcesprite; + + // Recreate sprite frame. Mirror the sprite if sourceframe matches the second frame block + spriteframe = new[] { new SpriteFrameInfo { Sprite = sprite, SpriteLongName = Lump.MakeLongName(sprite, true), + Mirror = (sprite.Length == 8 && sprite[6] == sourceframe) } }; + } + + return false; + } // Gather rotations string[] frames = new string[8]; bool[] mirror = new bool[8]; int processedcount = 0; - string sourcename = sprite.Substring(0, 4); - IEnumerable spritenames = General.Map.Data.GetSpriteNames(sourcename); // Process gathered sprites - char sourceframe = sprite[4]; foreach(string s in spritenames) { // Check first frame block @@ -596,7 +675,7 @@ namespace CodeImp.DoomBuilder.Config if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle)) { General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\""); - return; + return false; } // Sanity checks @@ -610,7 +689,7 @@ namespace CodeImp.DoomBuilder.Config if(targetangle < 1 || targetangle > 8) { General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Expected sprite angle in [1..8] range"); - return; + return false; } // Even more sanity checks @@ -640,7 +719,7 @@ namespace CodeImp.DoomBuilder.Config if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle)) { General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\""); - return; + return false; } // Sanity checks @@ -654,7 +733,7 @@ namespace CodeImp.DoomBuilder.Config if(targetangle < 1 || targetangle > 8) { General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Expected sprite angle in [1..8] range"); - return; + return false; } // Even more sanity checks @@ -697,7 +776,7 @@ namespace CodeImp.DoomBuilder.Config } General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Sprite rotations " + ma + " for sprite " + sourcename + ", frame " + sourceframe + " are missing"); - return; + return false; } // Create collection @@ -706,6 +785,12 @@ namespace CodeImp.DoomBuilder.Config { spriteframe[i] = new SpriteFrameInfo { Sprite = frames[i], SpriteLongName = Lump.MakeLongName(frames[i]), Mirror = mirror[i] }; } + + // Update preview sprite + sprite = spriteframe[1].Sprite; + + // Done + return false; } // This is used for sorting diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs index c42c5d3d..79b23711 100644 --- a/Source/Core/Data/DataManager.cs +++ b/Source/Core/Data/DataManager.cs @@ -1499,6 +1499,22 @@ namespace CodeImp.DoomBuilder.Data // This loads the sprites that we really need for things private int LoadThingSprites() { + //mxd. Get all sprite names + HashSet spritenames = new HashSet(StringComparer.Ordinal); + foreach(DataReader dr in containers) + { + IEnumerable result = dr.GetSpriteNames(); + if(result != null) spritenames.UnionWith(result); + } + + //mxd. Get names of all voxel models, which can be used "as is" (these do not require corresponding sprite to work) + HashSet voxelnames = new HashSet(StringComparer.Ordinal); + foreach(DataReader dr in containers) + { + IEnumerable result = dr.GetVoxelNames(); + if(result != null) voxelnames.UnionWith(result); + } + // Go for all things foreach(ThingTypeInfo ti in General.Map.Data.ThingTypes) { @@ -1506,51 +1522,69 @@ namespace CodeImp.DoomBuilder.Data if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) continue; //mxd //mxd. Find all sprite angles - ti.SetupSpriteFrame(); + bool isvoxel = ti.SetupSpriteFrame(spritenames, voxelnames); - //mxd. Load them all - foreach(SpriteFrameInfo info in ti.SpriteFrame) + //mxd. Create voxel sprite? + if(isvoxel) { - ImageData image = null; - - // Sprite not in our collection yet? - if(!sprites.ContainsKey(info.SpriteLongName)) + if(!sprites.ContainsKey(Lump.MakeLongName(ti.Sprite))) { - //mxd. Go for all opened containers - bool spritefound = false; - if(!string.IsNullOrEmpty(info.Sprite)) + // Make new voxel image + VoxelImage image = new VoxelImage(ti.Sprite, ti.Sprite); + + // Add to collection + sprites.Add(image.LongName, image); + + // Add to preview manager + previews.AddImage(image); + } + } + else + { + //mxd. Load all sprites + foreach(SpriteFrameInfo info in ti.SpriteFrame) + { + ImageData image = null; + + // Sprite not in our collection yet? + if(!sprites.ContainsKey(info.SpriteLongName)) { - for(int i = containers.Count - 1; i >= 0; i--) + //mxd. Go for all opened containers + bool spritefound = false; + if(!string.IsNullOrEmpty(info.Sprite)) { - // This contain provides this sprite? - if(containers[i].GetSpriteExists(info.Sprite)) + for(int i = containers.Count - 1; i >= 0; i--) { - spritefound = true; - break; + // This container provides this sprite? + if(containers[i].GetSpriteExists(info.Sprite)) + { + spritefound = true; + break; + } } } - } - if(spritefound) - { - // Make new sprite image - image = new SpriteImage(info.Sprite); + if(spritefound) + { + // Make new sprite image + image = new SpriteImage(info.Sprite); - // Add to collection - sprites.Add(info.SpriteLongName, image); + // Add to collection + sprites.Add(info.SpriteLongName, image); + } + else + { + General.ErrorLogger.Add(ErrorType.Error, "Unable to find sprite lump \"" + info.Sprite + "\" used by actor \"" + ti.Title + "\":" + ti.Index + ". Forgot to include required resources?"); + } } else { - General.ErrorLogger.Add(ErrorType.Error, "Unable to find sprite lump \"" + info.Sprite + "\" used by actor \"" + ti.Title + "\":" + ti.Index + ". Forgot to include required resources?"); + image = sprites[info.SpriteLongName]; } - } - else - { - image = sprites[info.SpriteLongName]; - } - // Add to preview manager - if(image != null) previews.AddImage(image); + // Add to preview manager + if(image != null) previews.AddImage(image); + } } } @@ -1566,7 +1600,7 @@ namespace CodeImp.DoomBuilder.Data // Go for all opened containers for(int i = containers.Count - 1; i >= 0; i--) { - // This contain provides this sprite? + // This container provides this sprite? Stream spritedata = containers[i].GetSpriteData(pname, ref spritelocation); if(spritedata != null) return spritedata; } @@ -1719,18 +1753,40 @@ namespace CodeImp.DoomBuilder.Data } //mxd. Returns all sprite names, which start with given string - internal IEnumerable GetSpriteNames(string startswith) + internal IEnumerable GetSpriteNames() { HashSet result = new HashSet(StringComparer.OrdinalIgnoreCase); foreach(DataReader reader in containers) - result.UnionWith(reader.GetSpriteNames(startswith)); + result.UnionWith(reader.GetSpriteNames()); return result; } #endregion + #region ================== mxd. Voxels + + // This returns a specific voxel stream + internal Stream GetVoxelData(string pname, ref string voxellocation) + { + if(!string.IsNullOrEmpty(pname)) + { + // Go for all opened containers + for(int i = containers.Count - 1; i >= 0; i--) + { + // This container provides this sprite? + Stream spritedata = containers[i].GetVoxelData(pname, ref voxellocation); + if(spritedata != null) return spritedata; + } + } + + // No such voxel found + return null; + } + + #endregion + #region ================== Things // This loads the things from Decorate @@ -2210,43 +2266,14 @@ namespace CodeImp.DoomBuilder.Data // Bail out when not supported by current game configuration if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return; - // Get names of all voxel models, which can be used "as is" - HashSet voxelnames = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach(DataReader dr in containers) - { - currentreader = dr; - - IEnumerable result = dr.GetVoxelNames(); - if(result == null) continue; - - foreach(string s in result) - { - if(!voxelnames.Contains(s)) voxelnames.Add(s); - } - } - - Dictionary> sprites = new Dictionary>(StringComparer.Ordinal); - // Go for all things + Dictionary> allsprites = new Dictionary>(StringComparer.Ordinal); foreach(ThingTypeInfo ti in thingtypes.Values) { // Valid sprite name? - string sprite; - - if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) - { - if(ti.Actor == null) continue; - sprite = ti.Actor.FindSuitableVoxel(voxelnames); - } - else - { - sprite = ti.Sprite; - } - - if(string.IsNullOrEmpty(sprite)) continue; - if(!sprites.ContainsKey(sprite)) sprites.Add(sprite, new List()); - sprites[sprite].Add(ti.Index); + if(string.IsNullOrEmpty(ti.Sprite) || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) continue; + if(!allsprites.ContainsKey(ti.Sprite)) allsprites.Add(ti.Sprite, new List()); + allsprites[ti.Sprite].Add(ti.Index); } VoxeldefParser parser = new VoxeldefParser(); @@ -2264,13 +2291,34 @@ namespace CodeImp.DoomBuilder.Data { foreach(KeyValuePair entry in parser.Entries) { - foreach(KeyValuePair> sc in sprites) + foreach(KeyValuePair> sc in allsprites) { - if(sc.Key.Contains(entry.Key)) + if(sc.Key.StartsWith(entry.Key, StringComparison.OrdinalIgnoreCase)) { - foreach(int id in sc.Value) - modeldefentries[id] = entry.Value; - processed.Add(entry.Key); + foreach(int id in sc.Value) modeldefentries[id] = entry.Value; + processed.Add(sc.Key); + + // Create preview image if it doesn't exist... + ImageData sprite = GetSpriteImage(sc.Key); + if(sprite == null) + { + // Make new voxel image + sprite = new VoxelImage(sc.Key, entry.Value.ModelNames[0]); + + // Add to collection + sprites.Add(sprite.LongName, sprite); + + // Add to preview manager + previews.AddImage(sprite); + } + + // Apply VOXELDEF settings to the preview image... + VoxelImage vi = sprite as VoxelImage; + if(vi != null) + { + vi.AngleOffset = (int)Math.Round(entry.Value.AngleOffset); + vi.OverridePalette = entry.Value.OverridePalette; + } } } } @@ -2286,19 +2334,18 @@ namespace CodeImp.DoomBuilder.Data currentreader = null; // Get voxel models - foreach(string voxelname in voxelnames) + foreach(KeyValuePair> sc in allsprites) { - if(processed.Contains(voxelname)) continue; - foreach(KeyValuePair> sc in sprites) + if(processed.Contains(sc.Key)) continue; + + VoxelImage vi = GetSpriteImage(sc.Key) as VoxelImage; + if(vi != null) { - if(sc.Key.Contains(voxelname)) - { - // It's a model without a definition, and it corresponds to a sprite we can display, so let's add it - ModelData data = new ModelData { IsVoxel = true }; - data.ModelNames.Add(voxelname); + // It's a model without a definition, and it corresponds to a sprite we can display, so let's add it + ModelData data = new ModelData { IsVoxel = true }; + data.ModelNames.Add(vi.VoxelName); - foreach(int id in sprites[sc.Key]) modeldefentries[id] = data; - } + foreach(int id in sc.Value) modeldefentries[id] = data; } } } diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs index 64b88021..8c009411 100644 --- a/Source/Core/Data/DataReader.cs +++ b/Source/Core/Data/DataReader.cs @@ -86,8 +86,6 @@ namespace CodeImp.DoomBuilder.Data { #region ================== Constants - protected const string SPRITE_NAME_PATTERN = "(?i)\\A[a-z0-9]{4}([a-z][0-9]{0,2})$"; //mxd - #endregion #region ================== Variables @@ -215,7 +213,7 @@ namespace CodeImp.DoomBuilder.Data public abstract bool GetSpriteExists(string pname); //mxd. When implemented, returns all sprites, which name starts with given string - public abstract HashSet GetSpriteNames(string startswith); + public abstract HashSet GetSpriteNames(); #endregion @@ -258,10 +256,10 @@ namespace CodeImp.DoomBuilder.Data public abstract IEnumerable GetCvarInfoData(); //mxd. When implemented, this returns the list of voxel model names - public abstract IEnumerable GetVoxelNames(); + public abstract HashSet GetVoxelNames(); //mxd. When implemented, this returns the voxel lump - public abstract Stream GetVoxelData(string name); + public abstract Stream GetVoxelData(string name, ref string voxellocation); #endregion diff --git a/Source/Core/Data/DirectoryReader.cs b/Source/Core/Data/DirectoryReader.cs index d07a2dce..0834643c 100644 --- a/Source/Core/Data/DirectoryReader.cs +++ b/Source/Core/Data/DirectoryReader.cs @@ -343,7 +343,7 @@ namespace CodeImp.DoomBuilder.Data #region ================== Voxels (mxd) //mxd. This finds and returns a voxel stream - public override Stream GetVoxelData(string name) + public override Stream GetVoxelData(string name, ref string voxellocation) { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); @@ -351,7 +351,7 @@ namespace CodeImp.DoomBuilder.Data // Find in any of the wad files for(int i = wads.Count - 1; i >= 0; i--) { - Stream voxel = wads[i].GetVoxelData(name); + Stream voxel = wads[i].GetVoxelData(name, ref voxellocation); if(voxel != null) return voxel; } @@ -362,6 +362,7 @@ namespace CodeImp.DoomBuilder.Data string filename = FindFirstFile(path, Path.GetFileName(name), true); if((filename != null) && FileExists(filename)) { + voxellocation = location.GetDisplayName(); return LoadFile(filename); } } diff --git a/Source/Core/Data/PK3Reader.cs b/Source/Core/Data/PK3Reader.cs index d0735d05..0483a52e 100644 --- a/Source/Core/Data/PK3Reader.cs +++ b/Source/Core/Data/PK3Reader.cs @@ -359,7 +359,7 @@ namespace CodeImp.DoomBuilder.Data #region ================== Voxels (mxd) //mxd. This finds and returns a voxel stream or null if no voxel was found - public override Stream GetVoxelData(string name) + public override Stream GetVoxelData(string name, ref string voxellocation) { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); @@ -367,7 +367,7 @@ namespace CodeImp.DoomBuilder.Data // Find in any of the wad files for(int i = wads.Count - 1; i >= 0; i--) { - Stream voxel = wads[i].GetVoxelData(name); + Stream voxel = wads[i].GetVoxelData(name, ref voxellocation); if(voxel != null) return voxel; } @@ -377,6 +377,7 @@ namespace CodeImp.DoomBuilder.Data string filename = FindFirstFile(VOXELS_DIR, pfilename, true); if((filename != null) && FileExists(filename)) { + voxellocation = location.GetDisplayName(); return LoadFile(filename); } diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs index b4d4efb9..53be08c9 100644 --- a/Source/Core/Data/PK3StructuredReader.cs +++ b/Source/Core/Data/PK3StructuredReader.cs @@ -422,8 +422,8 @@ namespace CodeImp.DoomBuilder.Data return new List(images.Values); } - //mxd. Returns all sprites, which name starts with given string - public override HashSet GetSpriteNames(string startswith) + //mxd. This returns all sprite names + public override HashSet GetSpriteNames() { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); @@ -434,11 +434,11 @@ namespace CodeImp.DoomBuilder.Data // Note the backward order, because the last wad's images have priority for(int i = wads.Count - 1; i >= 0; i--) { - result.UnionWith(wads[i].GetSpriteNames(startswith)); + result.UnionWith(wads[i].GetSpriteNames()); } // Load from out own files - string[] files = GetAllFilesWhichTitleStartsWith(SPRITES_DIR, startswith, true); + string[] files = GetAllFiles(SPRITES_DIR, true); foreach(string file in files) { // Some users tend to place all manner of graphics into the "Sprites" folder... @@ -553,22 +553,29 @@ namespace CodeImp.DoomBuilder.Data #region ================== VOXELDEF (mxd) //mxd. This returns the list of voxels, which can be used without VOXELDEF definition - public override IEnumerable GetVoxelNames() + public override HashSet GetVoxelNames() { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); - string[] files = GetAllFiles("voxels", false); - List voxels = new List(); - Regex spritename = new Regex(SPRITE_NAME_PATTERN); + HashSet result = new HashSet(); + // Load from wad files + // Note the backward order, because the last wad's images have priority + for(int i = wads.Count - 1; i >= 0; i--) + { + result.UnionWith(wads[i].GetVoxelNames()); + } + + // Load from out own files + string[] files = GetAllFiles("voxels", false); foreach(string t in files) { string s = Path.GetFileNameWithoutExtension(t).ToUpperInvariant(); - if(spritename.IsMatch(s)) voxels.Add(s); + if(WADReader.IsValidVoxelName(s)) result.Add(s); } - return voxels.ToArray(); + return result; } //mxd diff --git a/Source/Core/Data/SpriteImage.cs b/Source/Core/Data/SpriteImage.cs index 9d8fb4be..97e7be27 100644 --- a/Source/Core/Data/SpriteImage.cs +++ b/Source/Core/Data/SpriteImage.cs @@ -17,16 +17,22 @@ #region ================== Namespaces using System; -using CodeImp.DoomBuilder.IO; using System.IO; using System.Runtime.InteropServices; +using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Windows; #endregion namespace CodeImp.DoomBuilder.Data { - public sealed class SpriteImage : ImageData + public interface ISpriteImage //mxd + { + int OffsetX { get; } + int OffsetY { get; } + } + + public sealed class SpriteImage : ImageData, ISpriteImage { #region ================== Variables diff --git a/Source/Core/Data/VoxelImage.cs b/Source/Core/Data/VoxelImage.cs new file mode 100644 index 00000000..316e2e3c --- /dev/null +++ b/Source/Core/Data/VoxelImage.cs @@ -0,0 +1,325 @@ +#region ================== Namespaces + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using CodeImp.DoomBuilder.Rendering; +using CodeImp.DoomBuilder.Windows; + +#endregion + +namespace CodeImp.DoomBuilder.Data +{ + public sealed class VoxelImage : ImageData, ISpriteImage + { + #region ================== Variables + + private int offsetx; + private int offsety; + private readonly string voxelname; + private bool overridepalette; + private int angleoffset; + + #endregion + + #region ================== Properties + + public int OffsetX { get { return offsetx; } } + public int OffsetY { get { return offsety; } } + public string VoxelName { get { return voxelname; } } + public bool OverridePalette { get { return overridepalette; } internal set { overridepalette = value; } } + public int AngleOffset { get { return angleoffset; } internal set { angleoffset = value; } } + + #endregion + + #region ================== Constructor / Disposer + + // Constructor + internal VoxelImage(string name, string voxelname) + { + // Initialize + SetName(name); + this.voxelname = voxelname; + + // We have no destructor + GC.SuppressFinalize(this); + } + + #endregion + + #region ================== Methods + + override public void LoadImage() + { + // Do the loading + LocalLoadImage(); + + // Notify the main thread about the change to redraw display + IntPtr strptr = Marshal.StringToCoTaskMemAuto(this.Name); + General.SendMessage(General.MainWindow.Handle, (int)MainForm.ThreadMessages.SpriteDataLoaded, strptr.ToInt32(), 0); + } + + // This loads the image + protected unsafe override void LocalLoadImage() + { + // Leave when already loaded + if(this.IsImageLoaded) return; + + lock(this) + { + // Get the lump data stream + string voxellocation = string.Empty; //mxd + Stream lumpdata = General.Map.Data.GetVoxelData(voxelname, ref voxellocation); + if(lumpdata != null) + { + // Copy lump data to memory + lumpdata.Seek(0, SeekOrigin.Begin); + byte[] membytes = new byte[(int)lumpdata.Length]; + lumpdata.Read(membytes, 0, (int)lumpdata.Length); + + using(MemoryStream mem = new MemoryStream(membytes)) + { + mem.Seek(0, SeekOrigin.Begin); + PixelColor[] palette = new PixelColor[256]; + + // Create front projection image from the KVX + using(BinaryReader reader = new BinaryReader(mem, Encoding.ASCII)) + { + reader.ReadInt32(); //numbytes, we don't use that + int xsize = reader.ReadInt32(); + int ysize = reader.ReadInt32(); + int zsize = reader.ReadInt32(); + + // Sanity check + if(xsize == 0 || ysize == 0 || zsize == 0) + { + General.ErrorLogger.Add(ErrorType.Error, "Cannot create sprite image for voxel \"" + Path.Combine(voxellocation, voxelname) + + "\" for voxel drawing: voxel has invalid size (width: " + xsize + ", height: " + zsize + ", depth: " + ysize); + loadfailed = true; + return; + } + + int pivotx = (int)Math.Round(reader.ReadInt32() / 256f); + int pivoty = (int)Math.Round(reader.ReadInt32() / 256f); + int pivotz = (int)Math.Round(reader.ReadInt32() / 256f); + + // Read offsets + int[] xoffset = new int[xsize + 1]; // why is it xsize + 1, not xsize?.. + short[,] xyoffset = new short[xsize, ysize + 1]; // why is it ysize + 1, not ysize?.. + + for(int i = 0; i < xoffset.Length; i++) + { + xoffset[i] = reader.ReadInt32(); + } + + for(int x = 0; x < xsize; x++) + { + for(int y = 0; y < ysize + 1; y++) + { + xyoffset[x, y] = reader.ReadInt16(); + } + } + + // Read slabs + List offsets = new List(xsize * ysize); + for(int x = 0; x < xsize; x++) + { + for(int y = 0; y < ysize; y++) + { + offsets.Add(xoffset[x] + xyoffset[x, y] + 28); // for some reason offsets are counted from start of xoffset[]... + } + } + + int counter = 0; + int slabsend = (int)(reader.BaseStream.Length - 768); + + // Read palette + if(!overridepalette) + { + reader.BaseStream.Position = slabsend; + for(int i = 0; i < 256; i++) + { + byte r = (byte)(reader.ReadByte() * 4); + byte g = (byte)(reader.ReadByte() * 4); + byte b = (byte)(reader.ReadByte() * 4); + palette[i] = new PixelColor(255, r, g, b); + } + } + else + { + for(int i = 0; i < 256; i++) palette[i] = General.Map.Data.Palette[i]; + } + + // Populate projection pixels array + int imgwidth, imgheight, imgoffsetx; + bool checkalpha = false; + + // Convert angleoffsets to the nearest cardinal direction... + angleoffset = General.ClampAngle((angleoffset + 45) / 90 * 90); + + switch(angleoffset) + { + case 0: + imgwidth = xsize; + imgheight = zsize; + imgoffsetx = pivotx; + break; + + case 90: + imgwidth = ysize; + imgheight = zsize; + imgoffsetx = imgwidth - pivoty; + checkalpha = true; + break; + + case 180: + imgwidth = xsize; + imgheight = zsize; + imgoffsetx = imgwidth - pivotx; + checkalpha = true; + break; + + case 270: + imgwidth = ysize; + imgheight = zsize; + imgoffsetx = pivoty; + break; + + default: throw new InvalidDataException("Invalid AngleOffset"); + } + + int numpixels = imgwidth * imgheight; + PixelColor[] pixelsarr = new PixelColor[numpixels]; + + // Read pixel colors + for(int x = 0; x < xsize; x++) + { + for(int y = 0; y < ysize; y++) + { + reader.BaseStream.Position = offsets[counter]; + int next = (counter < offsets.Count - 1 ? offsets[counter + 1] : slabsend); + + // Read first color from the slab + while(reader.BaseStream.Position < next) + { + int ztop = reader.ReadByte(); + int zleng = reader.ReadByte(); + if(ztop + zleng > zsize) break; + byte flags = reader.ReadByte(); + + if(zleng > 0) + { + // Skip slab if no flags are given (otherwise some garbage pixels may be drawn) + if(flags == 0) + { + reader.BaseStream.Position += zleng; + continue; + } + + List colorindices = new List(zleng); + for(int i = 0; i < zleng; i++) + { + colorindices.Add(reader.ReadByte()); + } + + int z = ztop; + int cstart = 0; + while(z < ztop + zleng) + { + // Get pixel position + int pixelpos; + switch(angleoffset) + { + case 0: pixelpos = x + z * xsize; break; + case 90: pixelpos = y + z * ysize; break; + case 180: pixelpos = xsize - x - 1 + z * xsize; break; + case 270: pixelpos = ysize - y - 1 + z * ysize; break; + default: throw new InvalidDataException("Invalid AngleOffset"); + } + + // Add to projection pixels array + if((checkalpha && pixelsarr[pixelpos].a == 0) || !checkalpha) + pixelsarr[pixelpos] = palette[colorindices[cstart]]; + + // Increment counters + cstart++; + z++; + } + } + } + + counter++; + } + } + + // Draw to bitmap + if(bitmap != null) bitmap.Dispose(); + bitmap = new Bitmap(imgwidth, imgheight, PixelFormat.Format32bppArgb); + BitmapData bmpdata = null; + + try + { + bmpdata = bitmap.LockBits(new Rectangle(0, 0, imgwidth, imgheight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + } + catch(Exception e) + { + General.ErrorLogger.Add(ErrorType.Error, "Cannot lock image for drawing voxel \"" + + Path.Combine(voxellocation, voxelname) + "\". " + e.GetType().Name + ": " + e.Message); + bitmap = null; + } + + if(bmpdata != null) + { + // Apply pixels to image + PixelColor* pixels = (PixelColor*)bmpdata.Scan0.ToPointer(); + int i = 0; + + for(PixelColor* cp = pixels; cp < pixels + numpixels; cp++, i++) + { + if(pixelsarr[i].a == 255) + { + cp->r = pixelsarr[i].r; + cp->g = pixelsarr[i].g; + cp->b = pixelsarr[i].b; + cp->a = 255; + } + } + + bitmap.UnlockBits(bmpdata); + } + + if(bitmap != null) + { + // Get width and height from image + width = bitmap.Size.Width; + height = bitmap.Size.Height; + scale.x = 1.0f; + scale.y = 1.0f; + offsetx = imgoffsetx; + offsety = pivotz; + } + else + { + loadfailed = true; + } + } + } + } + else + { + // Missing voxel lump! + General.ErrorLogger.Add(ErrorType.Error, "Missing voxel lump \"" + voxelname + "\". Forgot to include required resources?"); + } + + // Pass on to base + base.LocalLoadImage(); + } + } + + #endregion + } +} diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs index 7e1d8551..f44cb6af 100644 --- a/Source/Core/Data/WADReader.cs +++ b/Source/Core/Data/WADReader.cs @@ -42,6 +42,9 @@ namespace CodeImp.DoomBuilder.Data private static readonly Regex sprite6 = new Regex(@"(\S{4}[A-Za-z\[\]\\]{1}[0-8]{1})"); private static readonly Regex sprite8 = new Regex(@"(\S{4}[A-Za-z\[\]\\]{1}[0-8]{1}[A-Za-z\[\]\\]{1}[0-8]{1})"); + //mxd. Voxel recognition. + private static readonly Regex voxel = new Regex(@"^\S{4}(([A-Za-z][0-9]){0,2}|[A-Za-z]{0,1})$"); + #endregion #region ================== Structures @@ -919,23 +922,17 @@ namespace CodeImp.DoomBuilder.Data return false; } - //mxd. Returns all sprites, which name starts with given string - public override HashSet GetSpriteNames(string startswith) + //mxd. This returns all sprite names in the WAD + public override HashSet GetSpriteNames() { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); HashSet result = new HashSet(); - if(startswith.Length > 8) return result; - - startswith = startswith.ToUpperInvariant(); foreach(LumpRange range in spriteranges) { for(int i = range.start; i < range.end + 1; i++) - { - if(file.Lumps[i].Name.StartsWith(startswith) && IsValidSpriteName(file.Lumps[i].Name)) - result.Add(file.Lumps[i].Name); - } + if(IsValidSpriteName(file.Lumps[i].Name)) result.Add(file.Lumps[i].Name); } return result; @@ -952,25 +949,23 @@ namespace CodeImp.DoomBuilder.Data #region ================== Voxels (mxd) //mxd. This returns the list of voxels, which can be used without VOXELDEF definition - public override IEnumerable GetVoxelNames() + public override HashSet GetVoxelNames() { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); - List voxels = new List(); - Regex spriteName = new Regex(SPRITE_NAME_PATTERN); - + HashSet result = new HashSet(); foreach(LumpRange range in voxelranges) { if(range.start == range.end) continue; for(int i = range.start + 1; i < range.end; i++) { - if(spriteName.IsMatch(file.Lumps[i].Name)) voxels.Add(file.Lumps[i].Name); + if(IsValidVoxelName(file.Lumps[i].Name)) result.Add(file.Lumps[i].Name); } } - return voxels.ToArray(); + return result; } //mxd @@ -981,7 +976,7 @@ namespace CodeImp.DoomBuilder.Data } //mxd. This finds and returns a voxel stream or null if no voxel was found - public override Stream GetVoxelData(string name) + public override Stream GetVoxelData(string name, ref string voxellocation) { // Error when suspended if(issuspended) throw new Exception("Data reader is suspended"); @@ -990,12 +985,22 @@ namespace CodeImp.DoomBuilder.Data { if(range.start == range.end) continue; Lump lump = file.FindLump(name, range.start, range.end); - if(lump != null) return lump.Stream; + if(lump != null) + { + voxellocation = location.GetDisplayName(); + return lump.Stream; + } } return null; } + //mxd + internal static bool IsValidVoxelName(string name) + { + return (name.Length > 3 && name.Length < 7) && voxel.IsMatch(name); + } + #endregion #region ================== Decorate, Gldefs, Mapinfo, etc... diff --git a/Source/Core/GZBuilder/Data/ModelData.cs b/Source/Core/GZBuilder/Data/ModelData.cs index edc58be7..3e2207ae 100644 --- a/Source/Core/GZBuilder/Data/ModelData.cs +++ b/Source/Core/GZBuilder/Data/ModelData.cs @@ -38,7 +38,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data internal Vector3 Scale { get { return scale; } } internal Matrix Transform { get { return (General.Settings.GZStretchView ? transformstretched : transform); } } - internal bool OverridePalette; //used for voxel models only + internal bool OverridePalette; // Used for voxel models only + internal float AngleOffset; // Used for voxel models only internal bool InheritActorPitch; internal bool InheritActorRoll; diff --git a/Source/Core/GZBuilder/md3/ModelReader.cs b/Source/Core/GZBuilder/md3/ModelReader.cs index 2ecba643..5d1799c8 100644 --- a/Source/Core/GZBuilder/md3/ModelReader.cs +++ b/Source/Core/GZBuilder/md3/ModelReader.cs @@ -58,12 +58,13 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3 private static void LoadKVX(ModelData mde, List containers, Device device) { 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); + Stream ms = dr.GetVoxelData(name, ref unused); if(ms == null) continue; //load kvx diff --git a/Source/Core/Map/Thing.cs b/Source/Core/Map/Thing.cs index 375ee534..aee4b410 100644 --- a/Source/Core/Map/Thing.cs +++ b/Source/Core/Map/Thing.cs @@ -36,7 +36,7 @@ namespace CodeImp.DoomBuilder.Map #region ================== Constants public const int NUM_ARGS = 5; - public static HashSet AlignableRenderModes = new HashSet + public static readonly HashSet AlignableRenderModes = new HashSet { ThingRenderMode.FLATSPRITE, ThingRenderMode.WALLSPRITE, ThingRenderMode.MODEL }; @@ -546,14 +546,9 @@ namespace CodeImp.DoomBuilder.Map // Check if the thing has model override if(General.Map.Data.ModeldefEntries.ContainsKey(type)) { - if(General.Map.Data.ModeldefEntries[type].LoadState == ModelLoadState.None) - { - if(General.Map.Data.ProcessModel(type)) rendermode = ThingRenderMode.MODEL; - } - else - { - rendermode = ThingRenderMode.MODEL; - } + ModelData md = General.Map.Data.ModeldefEntries[type]; + if((md.LoadState == ModelLoadState.None && General.Map.Data.ProcessModel(type)) || md.LoadState != ModelLoadState.None) + rendermode = (General.Map.Data.ModeldefEntries[type].IsVoxel ? ThingRenderMode.VOXEL : ThingRenderMode.MODEL); } // Update radian versions of pitch and roll @@ -575,6 +570,11 @@ namespace CodeImp.DoomBuilder.Map pitchrad = 0; break; + case ThingRenderMode.VOXEL: + rollrad = 0; + pitchrad = 0; + break; + default: throw new NotImplementedException("Unknown ThingRenderMode"); } } diff --git a/Source/Core/Config/RenderModeEnums.cs b/Source/Core/Rendering/RenderModeEnums.cs similarity index 75% rename from Source/Core/Config/RenderModeEnums.cs rename to Source/Core/Rendering/RenderModeEnums.cs index 3cbceb67..c47df554 100644 --- a/Source/Core/Config/RenderModeEnums.cs +++ b/Source/Core/Rendering/RenderModeEnums.cs @@ -1,4 +1,4 @@ -namespace CodeImp.DoomBuilder.Config +namespace CodeImp.DoomBuilder.Rendering { public enum ModelRenderMode { @@ -19,6 +19,7 @@ { NORMAL, MODEL, + VOXEL, WALLSPRITE, FLATSPRITE, } diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs index 84d1cf03..51b924ee 100644 --- a/Source/Core/Rendering/Renderer2D.cs +++ b/Source/Core/Rendering/Renderer2D.cs @@ -1175,7 +1175,7 @@ namespace CodeImp.DoomBuilder.Rendering if(!fixedcolor && t.Highlighted) continue; // Collect models - if(t.RenderMode == ThingRenderMode.MODEL) + if(t.RenderMode == ThingRenderMode.MODEL || t.RenderMode == ThingRenderMode.VOXEL) { if(!modelsByType.ContainsKey(t.Type)) modelsByType.Add(t.Type, new List()); modelsByType[t.Type].Add(t); @@ -1233,7 +1233,8 @@ namespace CodeImp.DoomBuilder.Rendering foreach(KeyValuePair> group in thingsByType) { // Skip when all things of this type will be rendered as models - if(group.Value[0].RenderMode == ThingRenderMode.MODEL && (General.Settings.GZDrawModelsMode == ModelRenderMode.ALL)) continue; + if((group.Value[0].RenderMode == ThingRenderMode.MODEL || group.Value[0].RenderMode == ThingRenderMode.VOXEL) + && (General.Settings.GZDrawModelsMode == ModelRenderMode.ALL)) continue; // Find thing information ThingTypeInfo info = General.Map.Data.GetThingInfo(group.Key); @@ -1285,7 +1286,8 @@ namespace CodeImp.DoomBuilder.Rendering foreach(Thing t in framegroup.Value) { - if(t.RenderMode == ThingRenderMode.MODEL && ((General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected) || (General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER && alpha == 1.0f))) + if((t.RenderMode == ThingRenderMode.MODEL || t.RenderMode == ThingRenderMode.VOXEL) + && ((General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected) || (General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER && alpha == 1.0f))) continue; bool forcespriterendering; diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs index 50fb83ae..479d8cd0 100644 --- a/Source/Core/Rendering/Renderer3D.cs +++ b/Source/Core/Rendering/Renderer3D.cs @@ -1238,8 +1238,17 @@ namespace CodeImp.DoomBuilder.Rendering //mxd private Matrix CreateThingPositionMatrix(VisualThing t) { + // Use normal ThingRenderMode when model rendering is disabled for this thing + ThingRenderMode rendermode = t.Thing.RenderMode; + if((t.Thing.RenderMode == ThingRenderMode.MODEL || t.Thing.RenderMode == ThingRenderMode.VOXEL) && + (General.Settings.GZDrawModelsMode == ModelRenderMode.NONE || + (General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && !t.Selected))) + { + rendermode = ThingRenderMode.NORMAL; + } + // Create the matrix for positioning - switch(t.Thing.RenderMode) + switch(rendermode) { case ThingRenderMode.NORMAL: if(t.Info.XYBillboard) // Apply billboarding? @@ -1266,6 +1275,7 @@ namespace CodeImp.DoomBuilder.Rendering case ThingRenderMode.WALLSPRITE: case ThingRenderMode.MODEL: + case ThingRenderMode.VOXEL: return Matrix.Scaling(t.Thing.ScaleX, t.Thing.ScaleX, t.Thing.ScaleY) * t.Position; default: throw new NotImplementedException("Unknown ThingRenderMode"); @@ -1722,7 +1732,7 @@ namespace CodeImp.DoomBuilder.Rendering } //mxd. Gather models - if(t.Thing.RenderMode == ThingRenderMode.MODEL && + if((t.Thing.RenderMode == ThingRenderMode.MODEL || t.Thing.RenderMode == ThingRenderMode.VOXEL) && (General.Settings.GZDrawModelsMode == ModelRenderMode.ALL || General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER || (General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected))) diff --git a/Source/Core/VisualModes/VisualThing.cs b/Source/Core/VisualModes/VisualThing.cs index e04f81bf..6c54b6f4 100644 --- a/Source/Core/VisualModes/VisualThing.cs +++ b/Source/Core/VisualModes/VisualThing.cs @@ -292,6 +292,7 @@ namespace CodeImp.DoomBuilder.VisualModes { // Don't do anything case ThingRenderMode.MODEL: break; + case ThingRenderMode.VOXEL: break; // Actor becomes a flat sprite which can be tilted with the use of the Pitch actor property. case ThingRenderMode.FLATSPRITE: @@ -407,7 +408,7 @@ namespace CodeImp.DoomBuilder.VisualModes } break;*/ - default: + case ThingRenderMode.NORMAL: if(info.RollSprite) { transform = Matrix.Translation(0f, 0f, -localcenterz) * Matrix.RotationY(-thing.RollRad) * Matrix.Translation(0f, 0f, localcenterz); @@ -420,6 +421,8 @@ namespace CodeImp.DoomBuilder.VisualModes } } break; + + default: throw new NotImplementedException("Unknown ThingRenderMode"); } } } diff --git a/Source/Core/ZDoom/ActorStructure.cs b/Source/Core/ZDoom/ActorStructure.cs index 5da6f145..e53f19e0 100644 --- a/Source/Core/ZDoom/ActorStructure.cs +++ b/Source/Core/ZDoom/ActorStructure.cs @@ -31,7 +31,7 @@ namespace CodeImp.DoomBuilder.ZDoom { #region ================== Constants - private readonly string[] SPRITE_POSTFIXES = new[] {"2C8", "2D8", "2A8", "2B8", "1C1", "1D1", "1A1", "1B1", "A2", "A1", "A0", "2", "1", "0" }; + //private readonly string[] SPRITE_POSTFIXES = new[] {"2C8", "2D8", "2A8", "2B8", "1C1", "1D1", "1A1", "1B1", "A2", "A1", "A0", "2", "1", "0" }; internal const string ACTOR_CLASS_SPECIAL_TOKENS = ":{}\n;,"; //mxd #endregion @@ -678,8 +678,8 @@ namespace CodeImp.DoomBuilder.ZDoom //mxd. Valid when internal or exists if(sprite.StartsWith(DataManager.INTERNAL_PREFIX, StringComparison.OrdinalIgnoreCase) || General.Map.Data.GetSpriteExists(sprite)) { - result.Sprite = sprite; //mxd - return result; + result.Sprite = sprite; + return result; } //mxd. Bitch and moan @@ -733,90 +733,10 @@ namespace CodeImp.DoomBuilder.ZDoom } } - if(!string.IsNullOrEmpty(result.Sprite)) - { - // The sprite name is not actually complete, we still have to append - // the direction characters to it. Find an existing sprite with direction. - foreach(string postfix in SPRITE_POSTFIXES) - { - if(General.Map.Data.GetSpriteExists(result.Sprite + postfix)) - { - result.Sprite += postfix; - return result; - } - } - } - - // No sprite found + //mxd. We've found something. Or not... + //Info: actual sprites are resolved in ThingTypeInfo.SetupSpriteFrame() return result; } - - //mxd. - ///TODO: rewrite this - public string FindSuitableVoxel(HashSet voxels) - { - string result = string.Empty; - - // Try the idle state - if(HasState("idle")) - { - StateStructure s = GetState("idle"); - StateStructure.FrameInfo info = s.GetSprite(0); - if(!string.IsNullOrEmpty(info.Sprite)) result = info.Sprite; - } - - // Try the see state - if(string.IsNullOrEmpty(result) && HasState("see")) - { - StateStructure s = GetState("see"); - StateStructure.FrameInfo info = s.GetSprite(0); - if(!string.IsNullOrEmpty(info.Sprite)) result = info.Sprite; - } - - // Try the inactive state - if(string.IsNullOrEmpty(result) && HasState("inactive")) - { - StateStructure s = GetState("inactive"); - StateStructure.FrameInfo info = s.GetSprite(0); - if(!string.IsNullOrEmpty(info.Sprite)) result = info.Sprite; - } - - // Try the spawn state - if(string.IsNullOrEmpty(result) && HasState("spawn")) - { - StateStructure s = GetState("spawn"); - StateStructure.FrameInfo info = s.GetSprite(0); - if(!string.IsNullOrEmpty(info.Sprite)) result = info.Sprite; - } - - // Still no sprite found? then just pick the first we can find - if(string.IsNullOrEmpty(result)) - { - Dictionary list = GetAllStates(); - foreach(StateStructure s in list.Values) - { - StateStructure.FrameInfo info = s.GetSprite(0); - if(!string.IsNullOrEmpty(info.Sprite)) - { - result = info.Sprite; - break; - } - } - } - - if(!string.IsNullOrEmpty(result)) - { - if(voxels.Contains(result)) return result; - - // The sprite name may be incomplete. Find an existing sprite with direction. - foreach(string postfix in SPRITE_POSTFIXES) - if(voxels.Contains(result + postfix)) return result + postfix; - } - - - // No voxel found - return ""; - } #endregion } diff --git a/Source/Core/ZDoom/StateStructure.cs b/Source/Core/ZDoom/StateStructure.cs index 83558ae2..60a3d793 100644 --- a/Source/Core/ZDoom/StateStructure.cs +++ b/Source/Core/ZDoom/StateStructure.cs @@ -66,8 +66,7 @@ namespace CodeImp.DoomBuilder.ZDoom while(parser.SkipWhitespace(true)) { // Read first token - string token = parser.ReadToken(); - token = token.ToLowerInvariant(); + string token = parser.ReadToken().ToLowerInvariant(); // One of the flow control statements? if((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail")) @@ -147,25 +146,31 @@ namespace CodeImp.DoomBuilder.ZDoom FrameInfo info = new FrameInfo(); //mxd if(spriteframes.Length > 0) { - // Make the sprite name - string spritename = token + spriteframes[0]; - spritename = spritename.ToUpperInvariant(); - - // Ignore some odd ZDoom things - if(!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#")) + //mxd. I'm not even 50% sure the parser handles all bizzare cases without shifting sprite name / frame blocks, + // so let's log it as a warning, not an error... + if(token.Length != 4) { - info.Sprite = spritename; //mxd - sprites.Add(info); + parser.LogWarning("Invalid sprite name \"" + token.ToUpperInvariant() + "\". Sprite names must be exactly 4 characters long"); + } + else + { + // Make the sprite name + string spritename = (token + spriteframes[0]).ToUpperInvariant(); + + // Ignore some odd ZDoom things + if(!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#")) + { + info.Sprite = spritename; //mxd + sprites.Add(info); + } } } // Continue until the end of the line + parser.SkipWhitespace(false); string t = parser.ReadToken(); while(!string.IsNullOrEmpty(t) && t != "\n") { - parser.SkipWhitespace(false); - t = parser.ReadToken().ToLowerInvariant(); - //mxd. Bright keyword support... if(t == "bright") { @@ -206,9 +211,23 @@ namespace CodeImp.DoomBuilder.ZDoom // Break out of this loop break; } - + //mxd. Function params start (those can span multiple lines) + else if(t == "(") + { + int bracelevel = 1; + while(!string.IsNullOrEmpty(token) && bracelevel > 0) + { + parser.SkipWhitespace(true); + token = parser.ReadToken(); + switch(token) + { + case "(": bracelevel++; break; + case ")": bracelevel--; break; + } + } + } //mxd. Because stuff like this is also valid: "Actor Oneliner { States { Spawn: WOOT A 1 A_FadeOut(0.1) Loop }}" - if(t == "}") + else if(t == "}") { // Rewind so that this scope end can be read again parser.DataStream.Seek(-1, SeekOrigin.Current); @@ -216,6 +235,10 @@ namespace CodeImp.DoomBuilder.ZDoom // Done here return; } + + // Read next token + parser.SkipWhitespace(false); + t = parser.ReadToken().ToLowerInvariant(); } } diff --git a/Source/Core/ZDoom/VoxeldefParser.cs b/Source/Core/ZDoom/VoxeldefParser.cs index 6e6e84bc..e1018352 100644 --- a/Source/Core/ZDoom/VoxeldefParser.cs +++ b/Source/Core/ZDoom/VoxeldefParser.cs @@ -61,13 +61,12 @@ namespace CodeImp.DoomBuilder.ZDoom return false; } - modelName = StripQuotes(token).ToLowerInvariant(); + modelName = StripQuotes(token).ToUpperInvariant(); } else if(token == "{") //read the settings { ModelData mde = new ModelData { IsVoxel = true }; float scale = 1.0f; - float angleoffset = 0; while(SkipWhitespace(true)) { @@ -79,7 +78,7 @@ namespace CodeImp.DoomBuilder.ZDoom if(!string.IsNullOrEmpty(modelName) && spriteNames.Count > 0) { mde.ModelNames.Add(modelName); - mde.SetTransform(Matrix.RotationZ(Angle2D.DegToRad(angleoffset)), Matrix.Identity, new Vector3(scale)); + mde.SetTransform(Matrix.RotationZ(Angle2D.DegToRad(mde.AngleOffset)), Matrix.Identity, new Vector3(scale)); foreach(string s in spriteNames) { @@ -104,7 +103,7 @@ namespace CodeImp.DoomBuilder.ZDoom if(!NextTokenIs("=")) return false; token = ReadToken(); - if(!ReadSignedFloat(token, ref angleoffset)) + if(!ReadSignedFloat(token, ref mde.AngleOffset)) { // Not numeric! ReportError("Expected AngleOffset value, but got \"" + token + "\""); diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs index 73fe4ea7..92e60c00 100644 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs @@ -635,15 +635,16 @@ namespace CodeImp.DoomBuilder.BuilderModes if(options.FitWidth) { float scalex, offsetx; + float linelength = (float)Math.Round(Sidedef.Line.Length); // Let's use ZDoom-compatible line length here if(options.FitAcrossSurfaces) { - scalex = Texture.ScaledWidth / (Sidedef.Line.Length * (options.GlobalBounds.Width / Sidedef.Line.Length)) * options.HorizontalRepeat; + scalex = Texture.ScaledWidth / (linelength * (options.GlobalBounds.Width / linelength)) * options.HorizontalRepeat; offsetx = (float)Math.Round((options.Bounds.X * scalex - Sidedef.OffsetX - options.ControlSideOffsetX) % Texture.Width, General.Map.FormatInterface.VertexDecimals); } else { - scalex = Texture.ScaledWidth / Sidedef.Line.Length * options.HorizontalRepeat; + scalex = Texture.ScaledWidth / linelength * options.HorizontalRepeat; offsetx = -Sidedef.OffsetX - options.ControlSideOffsetX; } diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs index 65c13f67..9217b07f 100644 --- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs +++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualThing.cs @@ -293,7 +293,7 @@ namespace CodeImp.DoomBuilder.BuilderModes // Determine sprite size and offset float radius = sprite.ScaledWidth * 0.5f; float height = sprite.ScaledHeight; - SpriteImage spriteimg = sprite as SpriteImage; + ISpriteImage spriteimg = sprite as ISpriteImage; if(spriteimg != null) { offsetx = radius - spriteimg.OffsetX;