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)"; 1 = "Base Key (Front)";
2 = "Governor's Key"; 2 = "Governor's Key";
3 = "Travel Passcard"; 3 = "Travel Passcard";
4 = "Blue ID Badge"; 4 = "ID Badge";
5 = "Prison Key"; 5 = "Prison Key";
6 = "Severed Hand"; 6 = "Severed Hand";
7 = "Power Key 1"; 7 = "Power Key 1";
8 = "Power Key 2"; 8 = "Power Key 2";
9 = "Power Key 3"; 9 = "Power Key 3";
10 = "Gold Key"; 10 = "Gold Key";
11 = "Gold ID Badge"; 11 = "ID Card";
12 = "Silver Key"; 12 = "Silver Key";
13 = "Oracle Key"; 13 = "Oracle Key";
14 = "Military ID"; 14 = "Military ID";

View file

@ -310,7 +310,7 @@ keywords
PolyWait = "void PolyWait(int polyid)"; 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."; 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."; 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_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)"; Radius_Quake2 = "void Radius_Quake2(int tid, int intensity, int duration, int damrad, int tremrad, str sound)";
Random = "int Random(int min, int max)"; Random = "int Random(int min, int max)";

View file

@ -128,6 +128,7 @@ keywords
A_BulletAttack = "A_BulletAttack"; A_BulletAttack = "A_BulletAttack";
A_MonsterRail = "A_MonsterRail"; 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_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_RadiusThrust = "A_RadiusThrust(int force, int distance[, int flags[, int fullthrustdistance]])";
A_Detonate = "A_Detonate"; A_Detonate = "A_Detonate";
A_ThrowGrenade = "bool A_ThrowGrenade(str spawntype[, float spawnheight[, float throwspeed_horz[, float throwspeed_vert[, bool useammo]]]])"; 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_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_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_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 //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_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)"; A_CheckCeiling = "state A_CheckCeiling(str state)\nstate A_CheckCeiling(int offset)";
@ -307,9 +308,9 @@ keywords
A_Light1 = "A_Light1"; A_Light1 = "A_Light1";
A_Light2 = "A_Light2"; A_Light2 = "A_Light2";
A_LightInverse = "A_LightInverse"; 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_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_Recoil = "A_Recoil(float force)";
A_ZoomFactor = "A_ZoomFactor[(float zoom = 1.0[, int flags = 0])]\nflags: ZOOM flags."; A_ZoomFactor = "A_ZoomFactor[(float zoom = 1.0[, int flags = 0])]\nflags: ZOOM flags.";
A_SetCrosshair = "A_SetCrosshair(int number)"; A_SetCrosshair = "A_SetCrosshair(int number)";
@ -378,7 +379,7 @@ keywords
A_SkelWhoosh = "A_SkelWhoosh"; A_SkelWhoosh = "A_SkelWhoosh";
A_StartFire = "A_StartFire"; A_StartFire = "A_StartFire";
A_FireCrackle = "A_FireCrackle"; 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"; A_BarrelDestroy = "A_BarrelDestroy";
//Miscellaneous functions not listed in the "Action functions" wiki article //Miscellaneous functions not listed in the "Action functions" wiki article
A_Bang4Cloud = "A_Bang4Cloud"; A_Bang4Cloud = "A_Bang4Cloud";
@ -1369,4 +1370,9 @@ constants
PSPF_ADDBOB; PSPF_ADDBOB;
PSPF_POWDOUBLE; PSPF_POWDOUBLE;
PSPF_CVARFAST; 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="Actions\HintsManager.cs" />
<Compile Include="Config\AllTexturesSet.cs" /> <Compile Include="Config\AllTexturesSet.cs" />
<Compile Include="Config\FlagTranslation.cs" /> <Compile Include="Config\FlagTranslation.cs" />
<Compile Include="Config\RenderModeEnums.cs" /> <Compile Include="Rendering\RenderModeEnums.cs" />
<Compile Include="Config\PasteOptions.cs" /> <Compile Include="Config\PasteOptions.cs" />
<Compile Include="Config\ScriptDocumentSettings.cs" /> <Compile Include="Config\ScriptDocumentSettings.cs" />
<Compile Include="Config\SectorEffectData.cs" /> <Compile Include="Config\SectorEffectData.cs" />
@ -825,6 +825,7 @@
<Compile Include="Data\PK3FileImage.cs" /> <Compile Include="Data\PK3FileImage.cs" />
<Compile Include="Data\PK3StructuredReader.cs" /> <Compile Include="Data\PK3StructuredReader.cs" />
<Compile Include="Data\DynamicBitmapImage.cs" /> <Compile Include="Data\DynamicBitmapImage.cs" />
<Compile Include="Data\VoxelImage.cs" />
<Compile Include="Editing\CustomThingsFilter.cs" /> <Compile Include="Editing\CustomThingsFilter.cs" />
<Compile Include="General\CRC.cs" /> <Compile Include="General\CRC.cs" />
<Compile Include="General\ErrorItem.cs" /> <Compile Include="General\ErrorItem.cs" />

View file

@ -24,6 +24,7 @@ using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.GZBuilder.Data; using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Map; using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.ZDoom; using CodeImp.DoomBuilder.ZDoom;
#endregion #endregion
@ -459,9 +460,8 @@ namespace CodeImp.DoomBuilder.Config
// Set sprite // Set sprite
StateStructure.FrameInfo info = actor.FindSuitableSprite(); //mxd StateStructure.FrameInfo info = actor.FindSuitableSprite(); //mxd
string suitablesprite = (locksprite ? string.Empty : info.Sprite); //mxd if(!locksprite && !string.IsNullOrEmpty(info.Sprite)) //mxd. Added locksprite property
if(!string.IsNullOrEmpty(suitablesprite)) sprite = info.Sprite;
sprite = suitablesprite;
else if(string.IsNullOrEmpty(sprite))//mxd else if(string.IsNullOrEmpty(sprite))//mxd
sprite = DataManager.INTERNAL_PREFIX + "unknownthing"; sprite = DataManager.INTERNAL_PREFIX + "unknownthing";
@ -548,42 +548,121 @@ namespace CodeImp.DoomBuilder.Config
if(blocking > THING_BLOCKING_NONE) errorcheck = THING_ERROR_INSIDE_STUCK; if(blocking > THING_BLOCKING_NONE) errorcheck = THING_ERROR_INSIDE_STUCK;
} }
//mxd. This tries to find all possible sprite rotations //mxd. This tries to find all possible sprite rotations. Returns true when voxel substitute exists
internal void SetupSpriteFrame() internal bool SetupSpriteFrame(HashSet<string> allspritenames, HashSet<string> allvoxelnames)
{ {
// Empty or internal sprites don't have rotations // Empty, invalid or internal sprites don't have rotations
if(string.IsNullOrEmpty(sprite) || sprite.StartsWith(DataManager.INTERNAL_PREFIX)) return; // 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 string sourcename = sprite.Substring(0, 4);
if(sprite.Length != 6 && sprite.Length != 8) return; 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 // Get sprite angle
string anglestr = sprite.Substring(5, 1); string anglestr = sourcesprite.Substring(5, 1);
int sourceangle; int sourceangle;
if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out 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 + "\""); General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + sourcesprite + "\"");
return; return false;
} }
if(sourceangle < 0 || sourceangle > 8) 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"); General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + sourcesprite + "\". Sprite angle must be in [0..8] range");
return; return false;
} }
// No rotations? // No rotations? Then spriteframe is already setup
if(sourceangle == 0) return; 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 // Gather rotations
string[] frames = new string[8]; string[] frames = new string[8];
bool[] mirror = new bool[8]; bool[] mirror = new bool[8];
int processedcount = 0; int processedcount = 0;
string sourcename = sprite.Substring(0, 4);
IEnumerable<string> spritenames = General.Map.Data.GetSpriteNames(sourcename);
// Process gathered sprites // Process gathered sprites
char sourceframe = sprite[4];
foreach(string s in spritenames) foreach(string s in spritenames)
{ {
// Check first frame block // Check first frame block
@ -596,7 +675,7 @@ namespace CodeImp.DoomBuilder.Config
if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle)) 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 + "\""); General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\"");
return; return false;
} }
// Sanity checks // Sanity checks
@ -610,7 +689,7 @@ namespace CodeImp.DoomBuilder.Config
if(targetangle < 1 || targetangle > 8) if(targetangle < 1 || targetangle > 8)
{ {
General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Expected sprite angle in [1..8] range"); 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 // Even more sanity checks
@ -640,7 +719,7 @@ namespace CodeImp.DoomBuilder.Config
if(!int.TryParse(anglestr, NumberStyles.Integer, CultureInfo.InvariantCulture, out targetangle)) 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 + "\""); General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ". Unable to get sprite angle from sprite \"" + s + "\"");
return; return false;
} }
// Sanity checks // Sanity checks
@ -654,7 +733,7 @@ namespace CodeImp.DoomBuilder.Config
if(targetangle < 1 || targetangle > 8) if(targetangle < 1 || targetangle > 8)
{ {
General.ErrorLogger.Add(ErrorType.Error, "Error in actor \"" + title + "\":" + index + ", sprite \"" + s + "\". Expected sprite angle in [1..8] range"); 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 // 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"); 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 // 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] }; 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 // 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 // This loads the sprites that we really need for things
private int LoadThingSprites() 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 // Go for all things
foreach(ThingTypeInfo ti in General.Map.Data.ThingTypes) 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 if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) continue; //mxd
//mxd. Find all sprite angles //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) foreach(SpriteFrameInfo info in ti.SpriteFrame)
{ {
ImageData image = null; ImageData image = null;
@ -1522,7 +1555,7 @@ namespace CodeImp.DoomBuilder.Data
{ {
for(int i = containers.Count - 1; i >= 0; i--) 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)) if(containers[i].GetSpriteExists(info.Sprite))
{ {
spritefound = true; spritefound = true;
@ -1553,6 +1586,7 @@ namespace CodeImp.DoomBuilder.Data
if(image != null) previews.AddImage(image); if(image != null) previews.AddImage(image);
} }
} }
}
// Output info // Output info
return sprites.Count; return sprites.Count;
@ -1566,7 +1600,7 @@ namespace CodeImp.DoomBuilder.Data
// Go for all opened containers // Go for all opened containers
for(int i = containers.Count - 1; i >= 0; i--) 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); Stream spritedata = containers[i].GetSpriteData(pname, ref spritelocation);
if(spritedata != null) return spritedata; if(spritedata != null) return spritedata;
} }
@ -1719,18 +1753,40 @@ namespace CodeImp.DoomBuilder.Data
} }
//mxd. Returns all sprite names, which start with given string //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); HashSet<string> result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach(DataReader reader in containers) foreach(DataReader reader in containers)
result.UnionWith(reader.GetSpriteNames(startswith)); result.UnionWith(reader.GetSpriteNames());
return result; return result;
} }
#endregion #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 #region ================== Things
// This loads the things from Decorate // This loads the things from Decorate
@ -2210,43 +2266,14 @@ namespace CodeImp.DoomBuilder.Data
// Bail out when not supported by current game configuration // Bail out when not supported by current game configuration
if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return; 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 // Go for all things
Dictionary<string, List<int>> allsprites = new Dictionary<string, List<int>>(StringComparer.Ordinal);
foreach(ThingTypeInfo ti in thingtypes.Values) foreach(ThingTypeInfo ti in thingtypes.Values)
{ {
// Valid sprite name? // Valid sprite name?
string sprite; 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>());
if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) allsprites[ti.Sprite].Add(ti.Index);
{
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);
} }
VoxeldefParser parser = new VoxeldefParser(); VoxeldefParser parser = new VoxeldefParser();
@ -2264,13 +2291,34 @@ namespace CodeImp.DoomBuilder.Data
{ {
foreach(KeyValuePair<string, ModelData> entry in parser.Entries) 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) foreach(int id in sc.Value) modeldefentries[id] = entry.Value;
modeldefentries[id] = entry.Value; processed.Add(sc.Key);
processed.Add(entry.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; currentreader = null;
// Get voxel models // Get voxel models
foreach(string voxelname in voxelnames) foreach(KeyValuePair<string, List<int>> sc in allsprites)
{ {
if(processed.Contains(voxelname)) continue; if(processed.Contains(sc.Key)) continue;
foreach(KeyValuePair<string, List<int>> sc in sprites)
{ VoxelImage vi = GetSpriteImage(sc.Key) as VoxelImage;
if(sc.Key.Contains(voxelname)) if(vi != null)
{ {
// It's a model without a definition, and it corresponds to a sprite we can display, so let's add it // 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 }; 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 #region ================== Constants
protected const string SPRITE_NAME_PATTERN = "(?i)\\A[a-z0-9]{4}([a-z][0-9]{0,2})$"; //mxd
#endregion #endregion
#region ================== Variables #region ================== Variables
@ -215,7 +213,7 @@ namespace CodeImp.DoomBuilder.Data
public abstract bool GetSpriteExists(string pname); public abstract bool GetSpriteExists(string pname);
//mxd. When implemented, returns all sprites, which name starts with given string //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 #endregion
@ -258,10 +256,10 @@ namespace CodeImp.DoomBuilder.Data
public abstract IEnumerable<TextResourceData> GetCvarInfoData(); public abstract IEnumerable<TextResourceData> GetCvarInfoData();
//mxd. When implemented, this returns the list of voxel model names //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 //mxd. When implemented, this returns the voxel lump
public abstract Stream GetVoxelData(string name); public abstract Stream GetVoxelData(string name, ref string voxellocation);
#endregion #endregion

View file

@ -343,7 +343,7 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Voxels (mxd) #region ================== Voxels (mxd)
//mxd. This finds and returns a voxel stream //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 // Error when suspended
if(issuspended) throw new Exception("Data reader is 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 // Find in any of the wad files
for(int i = wads.Count - 1; i >= 0; i--) 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; if(voxel != null) return voxel;
} }
@ -362,6 +362,7 @@ namespace CodeImp.DoomBuilder.Data
string filename = FindFirstFile(path, Path.GetFileName(name), true); string filename = FindFirstFile(path, Path.GetFileName(name), true);
if((filename != null) && FileExists(filename)) if((filename != null) && FileExists(filename))
{ {
voxellocation = location.GetDisplayName();
return LoadFile(filename); return LoadFile(filename);
} }
} }

View file

@ -359,7 +359,7 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Voxels (mxd) #region ================== Voxels (mxd)
//mxd. This finds and returns a voxel stream or null if no voxel was found //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 // Error when suspended
if(issuspended) throw new Exception("Data reader is 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 // Find in any of the wad files
for(int i = wads.Count - 1; i >= 0; i--) 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; if(voxel != null) return voxel;
} }
@ -377,6 +377,7 @@ namespace CodeImp.DoomBuilder.Data
string filename = FindFirstFile(VOXELS_DIR, pfilename, true); string filename = FindFirstFile(VOXELS_DIR, pfilename, true);
if((filename != null) && FileExists(filename)) if((filename != null) && FileExists(filename))
{ {
voxellocation = location.GetDisplayName();
return LoadFile(filename); return LoadFile(filename);
} }

View file

@ -422,8 +422,8 @@ namespace CodeImp.DoomBuilder.Data
return new List<ImageData>(images.Values); return new List<ImageData>(images.Values);
} }
//mxd. Returns all sprites, which name starts with given string //mxd. This returns all sprite names
public override HashSet<string> GetSpriteNames(string startswith) public override HashSet<string> GetSpriteNames()
{ {
// Error when suspended // Error when suspended
if(issuspended) throw new Exception("Data reader is 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 // Note the backward order, because the last wad's images have priority
for(int i = wads.Count - 1; i >= 0; i--) 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 // Load from out own files
string[] files = GetAllFilesWhichTitleStartsWith(SPRITES_DIR, startswith, true); string[] files = GetAllFiles(SPRITES_DIR, true);
foreach(string file in files) foreach(string file in files)
{ {
// Some users tend to place all manner of graphics into the "Sprites" folder... // Some users tend to place all manner of graphics into the "Sprites" folder...
@ -553,22 +553,29 @@ namespace CodeImp.DoomBuilder.Data
#region ================== VOXELDEF (mxd) #region ================== VOXELDEF (mxd)
//mxd. This returns the list of voxels, which can be used without VOXELDEF definition //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 // Error when suspended
if(issuspended) throw new Exception("Data reader is suspended"); if(issuspended) throw new Exception("Data reader is suspended");
string[] files = GetAllFiles("voxels", false); HashSet<string> result = new HashSet<string>();
List<string> voxels = new List<string>();
Regex spritename = new Regex(SPRITE_NAME_PATTERN);
// 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) foreach(string t in files)
{ {
string s = Path.GetFileNameWithoutExtension(t).ToUpperInvariant(); 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 //mxd

View file

@ -17,16 +17,22 @@
#region ================== Namespaces #region ================== Namespaces
using System; using System;
using CodeImp.DoomBuilder.IO;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Windows; using CodeImp.DoomBuilder.Windows;
#endregion #endregion
namespace CodeImp.DoomBuilder.Data 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 #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 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})"); 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 #endregion
#region ================== Structures #region ================== Structures
@ -919,23 +922,17 @@ namespace CodeImp.DoomBuilder.Data
return false; return false;
} }
//mxd. Returns all sprites, which name starts with given string //mxd. This returns all sprite names in the WAD
public override HashSet<string> GetSpriteNames(string startswith) public override HashSet<string> GetSpriteNames()
{ {
// Error when suspended // Error when suspended
if(issuspended) throw new Exception("Data reader is suspended"); if(issuspended) throw new Exception("Data reader is suspended");
HashSet<string> result = new HashSet<string>(); HashSet<string> result = new HashSet<string>();
if(startswith.Length > 8) return result;
startswith = startswith.ToUpperInvariant();
foreach(LumpRange range in spriteranges) foreach(LumpRange range in spriteranges)
{ {
for(int i = range.start; i < range.end + 1; i++) for(int i = range.start; i < range.end + 1; i++)
{ if(IsValidSpriteName(file.Lumps[i].Name)) result.Add(file.Lumps[i].Name);
if(file.Lumps[i].Name.StartsWith(startswith) && IsValidSpriteName(file.Lumps[i].Name))
result.Add(file.Lumps[i].Name);
}
} }
return result; return result;
@ -952,25 +949,23 @@ namespace CodeImp.DoomBuilder.Data
#region ================== Voxels (mxd) #region ================== Voxels (mxd)
//mxd. This returns the list of voxels, which can be used without VOXELDEF definition //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 // Error when suspended
if(issuspended) throw new Exception("Data reader is suspended"); if(issuspended) throw new Exception("Data reader is suspended");
List<string> voxels = new List<string>(); HashSet<string> result = new HashSet<string>();
Regex spriteName = new Regex(SPRITE_NAME_PATTERN);
foreach(LumpRange range in voxelranges) foreach(LumpRange range in voxelranges)
{ {
if(range.start == range.end) continue; if(range.start == range.end) continue;
for(int i = range.start + 1; i < range.end; i++) 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 //mxd
@ -981,7 +976,7 @@ namespace CodeImp.DoomBuilder.Data
} }
//mxd. This finds and returns a voxel stream or null if no voxel was found //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 // Error when suspended
if(issuspended) throw new Exception("Data reader is suspended"); if(issuspended) throw new Exception("Data reader is suspended");
@ -990,12 +985,22 @@ namespace CodeImp.DoomBuilder.Data
{ {
if(range.start == range.end) continue; if(range.start == range.end) continue;
Lump lump = file.FindLump(name, range.start, range.end); 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; return null;
} }
//mxd
internal static bool IsValidVoxelName(string name)
{
return (name.Length > 3 && name.Length < 7) && voxel.IsMatch(name);
}
#endregion #endregion
#region ================== Decorate, Gldefs, Mapinfo, etc... #region ================== Decorate, Gldefs, Mapinfo, etc...

View file

@ -38,7 +38,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
internal Vector3 Scale { get { return scale; } } internal Vector3 Scale { get { return scale; } }
internal Matrix Transform { get { return (General.Settings.GZStretchView ? transformstretched : transform); } } 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 InheritActorPitch;
internal bool InheritActorRoll; 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) private static void LoadKVX(ModelData mde, List<DataReader> containers, Device device)
{ {
mde.Model = new GZModel(); mde.Model = new GZModel();
string unused = string.Empty;
foreach(string name in mde.ModelNames) foreach(string name in mde.ModelNames)
{ {
//find the model //find the model
foreach(DataReader dr in containers) foreach(DataReader dr in containers)
{ {
Stream ms = dr.GetVoxelData(name); Stream ms = dr.GetVoxelData(name, ref unused);
if(ms == null) continue; if(ms == null) continue;
//load kvx //load kvx

View file

@ -36,7 +36,7 @@ namespace CodeImp.DoomBuilder.Map
#region ================== Constants #region ================== Constants
public const int NUM_ARGS = 5; 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 ThingRenderMode.FLATSPRITE, ThingRenderMode.WALLSPRITE, ThingRenderMode.MODEL
}; };
@ -546,14 +546,9 @@ namespace CodeImp.DoomBuilder.Map
// Check if the thing has model override // Check if the thing has model override
if(General.Map.Data.ModeldefEntries.ContainsKey(type)) if(General.Map.Data.ModeldefEntries.ContainsKey(type))
{ {
if(General.Map.Data.ModeldefEntries[type].LoadState == ModelLoadState.None) ModelData md = General.Map.Data.ModeldefEntries[type];
{ if((md.LoadState == ModelLoadState.None && General.Map.Data.ProcessModel(type)) || md.LoadState != ModelLoadState.None)
if(General.Map.Data.ProcessModel(type)) rendermode = ThingRenderMode.MODEL; rendermode = (General.Map.Data.ModeldefEntries[type].IsVoxel ? ThingRenderMode.VOXEL : ThingRenderMode.MODEL);
}
else
{
rendermode = ThingRenderMode.MODEL;
}
} }
// Update radian versions of pitch and roll // Update radian versions of pitch and roll
@ -575,6 +570,11 @@ namespace CodeImp.DoomBuilder.Map
pitchrad = 0; pitchrad = 0;
break; break;
case ThingRenderMode.VOXEL:
rollrad = 0;
pitchrad = 0;
break;
default: throw new NotImplementedException("Unknown ThingRenderMode"); default: throw new NotImplementedException("Unknown ThingRenderMode");
} }
} }

View file

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

View file

@ -1175,7 +1175,7 @@ namespace CodeImp.DoomBuilder.Rendering
if(!fixedcolor && t.Highlighted) continue; if(!fixedcolor && t.Highlighted) continue;
// Collect models // 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>()); if(!modelsByType.ContainsKey(t.Type)) modelsByType.Add(t.Type, new List<Thing>());
modelsByType[t.Type].Add(t); modelsByType[t.Type].Add(t);
@ -1233,7 +1233,8 @@ namespace CodeImp.DoomBuilder.Rendering
foreach(KeyValuePair<int, List<Thing>> group in thingsByType) foreach(KeyValuePair<int, List<Thing>> group in thingsByType)
{ {
// Skip when all things of this type will be rendered as models // 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 // Find thing information
ThingTypeInfo info = General.Map.Data.GetThingInfo(group.Key); ThingTypeInfo info = General.Map.Data.GetThingInfo(group.Key);
@ -1285,7 +1286,8 @@ namespace CodeImp.DoomBuilder.Rendering
foreach(Thing t in framegroup.Value) 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; continue;
bool forcespriterendering; bool forcespriterendering;

View file

@ -1238,8 +1238,17 @@ namespace CodeImp.DoomBuilder.Rendering
//mxd //mxd
private Matrix CreateThingPositionMatrix(VisualThing t) 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 // Create the matrix for positioning
switch(t.Thing.RenderMode) switch(rendermode)
{ {
case ThingRenderMode.NORMAL: case ThingRenderMode.NORMAL:
if(t.Info.XYBillboard) // Apply billboarding? if(t.Info.XYBillboard) // Apply billboarding?
@ -1266,6 +1275,7 @@ namespace CodeImp.DoomBuilder.Rendering
case ThingRenderMode.WALLSPRITE: case ThingRenderMode.WALLSPRITE:
case ThingRenderMode.MODEL: case ThingRenderMode.MODEL:
case ThingRenderMode.VOXEL:
return Matrix.Scaling(t.Thing.ScaleX, t.Thing.ScaleX, t.Thing.ScaleY) * t.Position; return Matrix.Scaling(t.Thing.ScaleX, t.Thing.ScaleX, t.Thing.ScaleY) * t.Position;
default: throw new NotImplementedException("Unknown ThingRenderMode"); default: throw new NotImplementedException("Unknown ThingRenderMode");
@ -1722,7 +1732,7 @@ namespace CodeImp.DoomBuilder.Rendering
} }
//mxd. Gather models //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.ALL ||
General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER || General.Settings.GZDrawModelsMode == ModelRenderMode.ACTIVE_THINGS_FILTER ||
(General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected))) (General.Settings.GZDrawModelsMode == ModelRenderMode.SELECTION && t.Selected)))

View file

@ -292,6 +292,7 @@ namespace CodeImp.DoomBuilder.VisualModes
{ {
// Don't do anything // Don't do anything
case ThingRenderMode.MODEL: break; 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. // Actor becomes a flat sprite which can be tilted with the use of the Pitch actor property.
case ThingRenderMode.FLATSPRITE: case ThingRenderMode.FLATSPRITE:
@ -407,7 +408,7 @@ namespace CodeImp.DoomBuilder.VisualModes
} }
break;*/ break;*/
default: case ThingRenderMode.NORMAL:
if(info.RollSprite) if(info.RollSprite)
{ {
transform = Matrix.Translation(0f, 0f, -localcenterz) * Matrix.RotationY(-thing.RollRad) * Matrix.Translation(0f, 0f, localcenterz); transform = Matrix.Translation(0f, 0f, -localcenterz) * Matrix.RotationY(-thing.RollRad) * Matrix.Translation(0f, 0f, localcenterz);
@ -420,6 +421,8 @@ namespace CodeImp.DoomBuilder.VisualModes
} }
} }
break; break;
default: throw new NotImplementedException("Unknown ThingRenderMode");
} }
} }
} }

View file

@ -31,7 +31,7 @@ namespace CodeImp.DoomBuilder.ZDoom
{ {
#region ================== Constants #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 internal const string ACTOR_CLASS_SPECIAL_TOKENS = ":{}\n;,"; //mxd
#endregion #endregion
@ -678,7 +678,7 @@ namespace CodeImp.DoomBuilder.ZDoom
//mxd. Valid when internal or exists //mxd. Valid when internal or exists
if(sprite.StartsWith(DataManager.INTERNAL_PREFIX, StringComparison.OrdinalIgnoreCase) || General.Map.Data.GetSpriteExists(sprite)) if(sprite.StartsWith(DataManager.INTERNAL_PREFIX, StringComparison.OrdinalIgnoreCase) || General.Map.Data.GetSpriteExists(sprite))
{ {
result.Sprite = sprite; //mxd result.Sprite = sprite;
return result; return result;
} }
@ -733,90 +733,10 @@ namespace CodeImp.DoomBuilder.ZDoom
} }
} }
if(!string.IsNullOrEmpty(result.Sprite)) //mxd. We've found something. Or not...
{ //Info: actual sprites are resolved in ThingTypeInfo.SetupSpriteFrame()
// 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; 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 #endregion
} }

View file

@ -66,8 +66,7 @@ namespace CodeImp.DoomBuilder.ZDoom
while(parser.SkipWhitespace(true)) while(parser.SkipWhitespace(true))
{ {
// Read first token // Read first token
string token = parser.ReadToken(); string token = parser.ReadToken().ToLowerInvariant();
token = token.ToLowerInvariant();
// One of the flow control statements? // One of the flow control statements?
if((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail")) if((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail"))
@ -146,10 +145,17 @@ namespace CodeImp.DoomBuilder.ZDoom
// No first sprite yet? // No first sprite yet?
FrameInfo info = new FrameInfo(); //mxd FrameInfo info = new FrameInfo(); //mxd
if(spriteframes.Length > 0) 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 // Make the sprite name
string spritename = token + spriteframes[0]; string spritename = (token + spriteframes[0]).ToUpperInvariant();
spritename = spritename.ToUpperInvariant();
// Ignore some odd ZDoom things // Ignore some odd ZDoom things
if(!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#")) if(!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#"))
@ -158,14 +164,13 @@ namespace CodeImp.DoomBuilder.ZDoom
sprites.Add(info); sprites.Add(info);
} }
} }
}
// Continue until the end of the line // Continue until the end of the line
parser.SkipWhitespace(false);
string t = parser.ReadToken(); string t = parser.ReadToken();
while(!string.IsNullOrEmpty(t) && t != "\n") while(!string.IsNullOrEmpty(t) && t != "\n")
{ {
parser.SkipWhitespace(false);
t = parser.ReadToken().ToLowerInvariant();
//mxd. Bright keyword support... //mxd. Bright keyword support...
if(t == "bright") if(t == "bright")
{ {
@ -206,9 +211,23 @@ namespace CodeImp.DoomBuilder.ZDoom
// Break out of this loop // Break out of this loop
break; 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 }}" //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 // Rewind so that this scope end can be read again
parser.DataStream.Seek(-1, SeekOrigin.Current); parser.DataStream.Seek(-1, SeekOrigin.Current);
@ -216,6 +235,10 @@ namespace CodeImp.DoomBuilder.ZDoom
// Done here // Done here
return; return;
} }
// Read next token
parser.SkipWhitespace(false);
t = parser.ReadToken().ToLowerInvariant();
} }
} }

View file

@ -61,13 +61,12 @@ namespace CodeImp.DoomBuilder.ZDoom
return false; return false;
} }
modelName = StripQuotes(token).ToLowerInvariant(); modelName = StripQuotes(token).ToUpperInvariant();
} }
else if(token == "{") //read the settings else if(token == "{") //read the settings
{ {
ModelData mde = new ModelData { IsVoxel = true }; ModelData mde = new ModelData { IsVoxel = true };
float scale = 1.0f; float scale = 1.0f;
float angleoffset = 0;
while(SkipWhitespace(true)) while(SkipWhitespace(true))
{ {
@ -79,7 +78,7 @@ namespace CodeImp.DoomBuilder.ZDoom
if(!string.IsNullOrEmpty(modelName) && spriteNames.Count > 0) if(!string.IsNullOrEmpty(modelName) && spriteNames.Count > 0)
{ {
mde.ModelNames.Add(modelName); 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) foreach(string s in spriteNames)
{ {
@ -104,7 +103,7 @@ namespace CodeImp.DoomBuilder.ZDoom
if(!NextTokenIs("=")) return false; if(!NextTokenIs("=")) return false;
token = ReadToken(); token = ReadToken();
if(!ReadSignedFloat(token, ref angleoffset)) if(!ReadSignedFloat(token, ref mde.AngleOffset))
{ {
// Not numeric! // Not numeric!
ReportError("Expected AngleOffset value, but got \"" + token + "\""); ReportError("Expected AngleOffset value, but got \"" + token + "\"");

View file

@ -635,15 +635,16 @@ namespace CodeImp.DoomBuilder.BuilderModes
if(options.FitWidth) if(options.FitWidth)
{ {
float scalex, offsetx; float scalex, offsetx;
float linelength = (float)Math.Round(Sidedef.Line.Length); // Let's use ZDoom-compatible line length here
if(options.FitAcrossSurfaces) 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); offsetx = (float)Math.Round((options.Bounds.X * scalex - Sidedef.OffsetX - options.ControlSideOffsetX) % Texture.Width, General.Map.FormatInterface.VertexDecimals);
} }
else else
{ {
scalex = Texture.ScaledWidth / Sidedef.Line.Length * options.HorizontalRepeat; scalex = Texture.ScaledWidth / linelength * options.HorizontalRepeat;
offsetx = -Sidedef.OffsetX - options.ControlSideOffsetX; offsetx = -Sidedef.OffsetX - options.ControlSideOffsetX;
} }

View file

@ -293,7 +293,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
// Determine sprite size and offset // Determine sprite size and offset
float radius = sprite.ScaledWidth * 0.5f; float radius = sprite.ScaledWidth * 0.5f;
float height = sprite.ScaledHeight; float height = sprite.ScaledHeight;
SpriteImage spriteimg = sprite as SpriteImage; ISpriteImage spriteimg = sprite as ISpriteImage;
if(spriteimg != null) if(spriteimg != null)
{ {
offsetx = radius - spriteimg.OffsetX; offsetx = radius - spriteimg.OffsetX;