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.
This commit is contained in:
MaxED 2016-07-11 22:13:43 +00:00
parent 3677221efa
commit ec9c3a71f9
25 changed files with 720 additions and 277 deletions

View file

@ -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";

View file

@ -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)";

View file

@ -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";
@ -1369,4 +1370,9 @@ constants
PSPF_ADDBOB;
PSPF_POWDOUBLE;
PSPF_CVARFAST;
//A_BFGSpray flags
BFGF_MISSILEORIGIN;
BFGF_HURTSOURCE;
//A_RadiusDamageSelf flags
RDSF_BFGDAMAGE;
}

View file

@ -705,7 +705,7 @@
<Compile Include="Actions\HintsManager.cs" />
<Compile Include="Config\AllTexturesSet.cs" />
<Compile Include="Config\FlagTranslation.cs" />
<Compile Include="Config\RenderModeEnums.cs" />
<Compile Include="Rendering\RenderModeEnums.cs" />
<Compile Include="Config\PasteOptions.cs" />
<Compile Include="Config\ScriptDocumentSettings.cs" />
<Compile Include="Config\SectorEffectData.cs" />
@ -825,6 +825,7 @@
<Compile Include="Data\PK3FileImage.cs" />
<Compile Include="Data\PK3StructuredReader.cs" />
<Compile Include="Data\DynamicBitmapImage.cs" />
<Compile Include="Data\VoxelImage.cs" />
<Compile Include="Editing\CustomThingsFilter.cs" />
<Compile Include="General\CRC.cs" />
<Compile Include="General\ErrorItem.cs" />

View file

@ -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<string> allspritenames, HashSet<string> 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<string> voxelnames = new HashSet<string>();
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<string> spritenames = new HashSet<string>();
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<string> 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

View file

@ -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<string> spritenames = new HashSet<string>(StringComparer.Ordinal);
foreach(DataReader dr in containers)
{
IEnumerable<string> 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<string> voxelnames = new HashSet<string>(StringComparer.Ordinal);
foreach(DataReader dr in containers)
{
IEnumerable<string> result = dr.GetVoxelNames();
if(result != null) voxelnames.UnionWith(result);
}
// Go for all things
foreach(ThingTypeInfo ti in General.Map.Data.ThingTypes)
{
@ -1506,9 +1522,26 @@ 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
//mxd. Create voxel sprite?
if(isvoxel)
{
if(!sprites.ContainsKey(Lump.MakeLongName(ti.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;
@ -1522,7 +1555,7 @@ namespace CodeImp.DoomBuilder.Data
{
for(int i = containers.Count - 1; i >= 0; i--)
{
// This contain provides this sprite?
// This container provides this sprite?
if(containers[i].GetSpriteExists(info.Sprite))
{
spritefound = true;
@ -1553,6 +1586,7 @@ namespace CodeImp.DoomBuilder.Data
if(image != null) previews.AddImage(image);
}
}
}
// Output info
return sprites.Count;
@ -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<string> GetSpriteNames(string startswith)
internal IEnumerable<string> GetSpriteNames()
{
HashSet<string> result = new HashSet<string>(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<string> voxelnames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach(DataReader dr in containers)
{
currentreader = dr;
IEnumerable<string> result = dr.GetVoxelNames();
if(result == null) continue;
foreach(string s in result)
{
if(!voxelnames.Contains(s)) voxelnames.Add(s);
}
}
Dictionary<string, List<int>> sprites = new Dictionary<string, List<int>>(StringComparer.Ordinal);
// Go for all things
Dictionary<string, List<int>> allsprites = new Dictionary<string, List<int>>(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<int>());
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<int>());
allsprites[ti.Sprite].Add(ti.Index);
}
VoxeldefParser parser = new VoxeldefParser();
@ -2264,13 +2291,34 @@ namespace CodeImp.DoomBuilder.Data
{
foreach(KeyValuePair<string, ModelData> entry in parser.Entries)
{
foreach(KeyValuePair<string, List<int>> sc in sprites)
foreach(KeyValuePair<string, List<int>> 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<string, List<int>> sc in allsprites)
{
if(processed.Contains(voxelname)) continue;
foreach(KeyValuePair<string, List<int>> sc in sprites)
{
if(sc.Key.Contains(voxelname))
if(processed.Contains(sc.Key)) continue;
VoxelImage vi = GetSpriteImage(sc.Key) as VoxelImage;
if(vi != null)
{
// 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);
data.ModelNames.Add(vi.VoxelName);
foreach(int id in sprites[sc.Key]) modeldefentries[id] = data;
}
foreach(int id in sc.Value) modeldefentries[id] = data;
}
}
}

View file

@ -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<string> GetSpriteNames(string startswith);
public abstract HashSet<string> GetSpriteNames();
#endregion
@ -258,10 +256,10 @@ namespace CodeImp.DoomBuilder.Data
public abstract IEnumerable<TextResourceData> GetCvarInfoData();
//mxd. When implemented, this returns the list of voxel model names
public abstract IEnumerable<string> GetVoxelNames();
public abstract HashSet<string> 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

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -422,8 +422,8 @@ namespace CodeImp.DoomBuilder.Data
return new List<ImageData>(images.Values);
}
//mxd. Returns all sprites, which name starts with given string
public override HashSet<string> GetSpriteNames(string startswith)
//mxd. This returns all sprite names
public override HashSet<string> 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<string> GetVoxelNames()
public override HashSet<string> GetVoxelNames()
{
// Error when suspended
if(issuspended) throw new Exception("Data reader is suspended");
string[] files = GetAllFiles("voxels", false);
List<string> voxels = new List<string>();
Regex spritename = new Regex(SPRITE_NAME_PATTERN);
HashSet<string> result = new HashSet<string>();
// 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

View file

@ -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

View file

@ -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<int> offsets = new List<int>(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<int> colorindices = new List<int>(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
}
}

View file

@ -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<string> GetSpriteNames(string startswith)
//mxd. This returns all sprite names in the WAD
public override HashSet<string> GetSpriteNames()
{
// Error when suspended
if(issuspended) throw new Exception("Data reader is suspended");
HashSet<string> result = new HashSet<string>();
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<string> GetVoxelNames()
public override HashSet<string> GetVoxelNames()
{
// Error when suspended
if(issuspended) throw new Exception("Data reader is suspended");
List<string> voxels = new List<string>();
Regex spriteName = new Regex(SPRITE_NAME_PATTERN);
HashSet<string> result = new HashSet<string>();
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...

View file

@ -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;

View file

@ -58,12 +58,13 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
private static void LoadKVX(ModelData mde, List<DataReader> 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

View file

@ -36,7 +36,7 @@ namespace CodeImp.DoomBuilder.Map
#region ================== Constants
public const int NUM_ARGS = 5;
public static HashSet<ThingRenderMode> AlignableRenderModes = new HashSet<ThingRenderMode>
public static readonly HashSet<ThingRenderMode> AlignableRenderModes = new HashSet<ThingRenderMode>
{
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");
}
}

View file

@ -1,4 +1,4 @@
namespace CodeImp.DoomBuilder.Config
namespace CodeImp.DoomBuilder.Rendering
{
public enum ModelRenderMode
{
@ -19,6 +19,7 @@
{
NORMAL,
MODEL,
VOXEL,
WALLSPRITE,
FLATSPRITE,
}

View file

@ -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<Thing>());
modelsByType[t.Type].Add(t);
@ -1233,7 +1233,8 @@ namespace CodeImp.DoomBuilder.Rendering
foreach(KeyValuePair<int, List<Thing>> 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;

View file

@ -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)))

View file

@ -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");
}
}
}

View file

@ -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,7 +678,7 @@ 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
result.Sprite = sprite;
return result;
}
@ -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;
//mxd. We've found something. Or not...
//Info: actual sprites are resolved in ThingTypeInfo.SetupSpriteFrame()
return result;
}
}
}
// No sprite found
return result;
}
//mxd.
///TODO: rewrite this
public string FindSuitableVoxel(HashSet<string> 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<string, StateStructure> 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
}

View file

@ -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"))
@ -146,10 +145,17 @@ namespace CodeImp.DoomBuilder.ZDoom
// No first sprite yet?
FrameInfo info = new FrameInfo(); //mxd
if(spriteframes.Length > 0)
{
//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)
{
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];
spritename = spritename.ToUpperInvariant();
string spritename = (token + spriteframes[0]).ToUpperInvariant();
// Ignore some odd ZDoom things
if(!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#"))
@ -158,14 +164,13 @@ namespace CodeImp.DoomBuilder.ZDoom
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();
}
}

View file

@ -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 + "\"");

View file

@ -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;
}

View file

@ -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;