2013-08-08 11:04:13 +00:00
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
2012-04-17 19:13:47 +00:00
using System.IO ;
using System.Drawing ;
2012-06-03 23:36:53 +00:00
using System.Drawing.Imaging ;
2019-06-15 15:44:02 +00:00
using System.Globalization ; // biwa
2012-04-17 19:13:47 +00:00
using System.Text ;
using System.Collections.Generic ;
2012-06-03 23:36:53 +00:00
using CodeImp.DoomBuilder.IO ;
2012-05-21 23:51:32 +00:00
using CodeImp.DoomBuilder.Data ;
2012-04-18 19:34:11 +00:00
using CodeImp.DoomBuilder.Rendering ;
2012-04-17 19:13:47 +00:00
using CodeImp.DoomBuilder.GZBuilder.Data ;
2013-04-11 09:27:16 +00:00
using CodeImp.DoomBuilder.Geometry ;
2019-08-08 18:06:35 +00:00
using System.Linq ;
2012-04-17 19:13:47 +00:00
2013-08-08 11:04:13 +00:00
#endregion
2012-04-17 19:13:47 +00:00
//mxd. Original version taken from here: http://colladadotnet.codeplex.com/SourceControl/changeset/view/40680
2012-05-21 23:51:32 +00:00
namespace CodeImp.DoomBuilder.GZBuilder.MD3
{
2013-03-18 13:52:27 +00:00
internal static class ModelReader
2013-08-08 11:04:13 +00:00
{
#region = = = = = = = = = = = = = = = = = = Variables
2016-01-14 11:39:52 +00:00
internal class MD3LoadResult
2013-03-18 13:52:27 +00:00
{
public List < string > Skins ;
public List < Mesh > Meshes ;
public string Errors ;
2018-07-05 09:50:03 +00:00
public MD3LoadResult ( )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
Skins = new List < string > ( ) ;
Meshes = new List < Mesh > ( ) ;
}
}
2013-08-08 11:04:13 +00:00
2014-01-03 10:33:45 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Load
2019-08-08 01:19:11 +00:00
public static void Load ( ModelData mde , List < DataReader > containers )
2014-12-03 23:15:26 +00:00
{
2019-08-08 01:19:11 +00:00
if ( mde . IsVoxel ) LoadKVX ( mde , containers ) ;
else LoadModel ( mde , containers ) ;
2014-01-03 10:33:45 +00:00
}
2019-08-08 01:19:11 +00:00
private static void LoadKVX ( ModelData mde , List < DataReader > containers )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
mde . Model = new GZModel ( ) ;
2016-07-11 22:13:43 +00:00
string unused = string . Empty ;
2015-12-28 15:01:53 +00:00
foreach ( string name in mde . ModelNames )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
//find the model
2018-07-05 09:50:03 +00:00
foreach ( DataReader dr in containers )
2014-12-03 23:15:26 +00:00
{
2016-07-11 22:13:43 +00:00
Stream ms = dr . GetVoxelData ( name , ref unused ) ;
2014-01-03 10:33:45 +00:00
if ( ms = = null ) continue ;
//load kvx
2019-08-08 01:19:11 +00:00
ReadKVX ( mde , ms ) ;
2014-01-03 10:33:45 +00:00
//done
ms . Close ( ) ;
break ;
}
}
//clear unneeded data
2016-07-18 12:05:19 +00:00
mde . SkinNames = null ;
2014-01-03 10:33:45 +00:00
mde . ModelNames = null ;
2018-07-05 09:50:03 +00:00
if ( mde . Model . Meshes = = null | | mde . Model . Meshes . Count = = 0 )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
mde . Model = null ;
}
}
2019-08-08 01:19:11 +00:00
private static void LoadModel ( ModelData mde , List < DataReader > containers )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
mde . Model = new GZModel ( ) ;
BoundingBoxSizes bbs = new BoundingBoxSizes ( ) ;
MD3LoadResult result = new MD3LoadResult ( ) ;
2013-08-08 11:04:13 +00:00
2013-09-11 09:47:53 +00:00
//load models and textures
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < mde . ModelNames . Count ; i + + )
2014-12-03 23:15:26 +00:00
{
2016-07-18 12:05:19 +00:00
// Use model skins?
2018-07-05 09:50:03 +00:00
// INFO: Skin MODELDEF property overrides both embedded surface names and ones set using SurfaceSkin MODELDEF property
2016-07-18 12:05:19 +00:00
Dictionary < int , string > skins = null ;
if ( string . IsNullOrEmpty ( mde . SkinNames [ i ] ) )
{
skins = ( mde . SurfaceSkinNames [ i ] . Count > 0 ? mde . SurfaceSkinNames [ i ] : new Dictionary < int , string > ( ) ) ;
}
2018-07-05 09:50:03 +00:00
2016-07-18 12:05:19 +00:00
// Load mesh
2013-03-18 13:52:27 +00:00
MemoryStream ms = LoadFile ( containers , mde . ModelNames [ i ] , true ) ;
2018-07-05 09:50:03 +00:00
if ( ms = = null )
2014-12-03 23:15:26 +00:00
{
2016-02-22 08:04:06 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + mde . ModelNames [ i ] + "\": unable to find file." ) ;
2013-07-30 09:25:27 +00:00
continue ;
}
2013-03-18 13:52:27 +00:00
string ext = Path . GetExtension ( mde . ModelNames [ i ] ) ;
2015-11-17 12:22:49 +00:00
switch ( ext )
2015-07-15 09:09:47 +00:00
{
case ".md3" :
2015-11-17 12:22:49 +00:00
if ( ! string . IsNullOrEmpty ( mde . FrameNames [ i ] ) )
{
2016-02-22 08:04:06 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + mde . ModelNames [ i ] + "\": frame names are not supported for MD3 models!" ) ;
2015-11-17 12:22:49 +00:00
continue ;
}
2019-08-08 01:19:11 +00:00
result = ReadMD3Model ( ref bbs , skins , ms , mde . FrameIndices [ i ] ) ;
2015-07-15 09:09:47 +00:00
break ;
case ".md2" :
2019-08-08 01:19:11 +00:00
result = ReadMD2Model ( ref bbs , ms , mde . FrameIndices [ i ] , mde . FrameNames [ i ] ) ;
2015-07-15 09:09:47 +00:00
break ;
2018-05-27 05:53:54 +00:00
case ".3d" :
2019-08-08 01:19:11 +00:00
result = Read3DModel ( ref bbs , skins , ms , mde . FrameIndices [ i ] , mde . ModelNames [ i ] , containers ) ;
2018-05-27 05:53:54 +00:00
break ;
2019-06-15 15:44:02 +00:00
case ".obj" :
// OBJ doesn't support frames, so print out an error
if ( mde . FrameIndices [ i ] > 0 )
{
General . ErrorLogger . Add ( ErrorType . Error , "Trying to load frame " + mde . FrameIndices [ i ] + " of model \"" + mde . ModelNames [ i ] + "\", but OBJ doesn't support frames!" ) ;
continue ;
}
2019-08-08 02:07:00 +00:00
result = ReadOBJModel ( ref bbs , skins , ms , mde . ModelNames [ i ] ) ;
2019-06-15 15:44:02 +00:00
break ;
2015-07-15 09:09:47 +00:00
default :
result . Errors = "model format is not supported" ;
break ;
}
2013-03-18 13:52:27 +00:00
ms . Close ( ) ;
2018-05-27 05:53:54 +00:00
if ( result = = null )
continue ;
2013-03-18 13:52:27 +00:00
2018-05-27 05:53:54 +00:00
//got errors?
2018-07-05 09:50:03 +00:00
if ( ! String . IsNullOrEmpty ( result . Errors ) )
2014-12-03 23:15:26 +00:00
{
2016-02-22 08:04:06 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + mde . ModelNames [ i ] + "\": " + result . Errors ) ;
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
//add loaded data to ModeldefEntry
mde . Model . Meshes . AddRange ( result . Meshes ) ;
//load texture
List < string > errors = new List < string > ( ) ;
2015-12-17 10:07:28 +00:00
// Texture not defined in MODELDEF?
2018-07-05 09:50:03 +00:00
if ( skins ! = null )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
//try to use model's own skins
for ( int m = 0 ; m < result . Meshes . Count ; m + + )
2014-12-03 23:15:26 +00:00
{
2019-06-15 15:44:02 +00:00
// biwa. Makes sure to add a dummy texture if the MODELDEF skin definition is erroneous
if ( m > = result . Skins . Count )
2014-12-03 23:15:26 +00:00
{
2019-06-15 15:44:02 +00:00
errors . Add ( "no skin defined for mesh " + m + "." ) ;
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
continue ;
}
2018-07-05 09:50:03 +00:00
if ( string . IsNullOrEmpty ( result . Skins [ m ] ) )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
errors . Add ( "texture not found in MODELDEF or model skin." ) ;
continue ;
}
string path = result . Skins [ m ] . Replace ( Path . AltDirectorySeparatorChar , Path . DirectorySeparatorChar ) ;
2019-06-16 16:40:10 +00:00
if ( ! String . IsNullOrEmpty ( mde . Path ) )
path = Path . Combine ( mde . Path , path ) ;
2019-08-08 02:07:00 +00:00
Texture t = GetTexture ( containers , path ) ;
2013-03-18 13:52:27 +00:00
2019-06-16 17:31:44 +00:00
if ( t ! = null )
2014-12-03 23:15:26 +00:00
{
2019-06-16 17:31:44 +00:00
mde . Model . Textures . Add ( t ) ;
2013-03-18 13:52:27 +00:00
continue ;
}
2019-06-16 17:31:44 +00:00
// That didn't work, let's try to load the texture without the additional path
path = result . Skins [ m ] . Replace ( Path . AltDirectorySeparatorChar , Path . DirectorySeparatorChar ) ;
2019-08-08 02:07:00 +00:00
t = GetTexture ( containers , path ) ;
2013-03-18 13:52:27 +00:00
2019-06-16 17:31:44 +00:00
if ( t = = null )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
2019-06-16 17:31:44 +00:00
errors . Add ( "unable to load skin \"" + path + "\"" ) ;
2013-03-18 13:52:27 +00:00
continue ;
2018-07-05 09:50:03 +00:00
}
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( t ) ;
}
2015-12-17 10:07:28 +00:00
}
//Try to use texture loaded from MODELDEFS
else
2014-12-03 23:15:26 +00:00
{
2019-08-08 02:07:00 +00:00
Texture t = GetTexture ( containers , mde . SkinNames [ i ] ) ;
2019-06-15 15:44:02 +00:00
2018-07-05 09:50:03 +00:00
if ( t = = null )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
2019-06-15 15:44:02 +00:00
errors . Add ( "unable to load skin \"" + mde . SkinNames [ i ] + "\"" ) ;
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( t ) ;
}
}
2012-04-17 19:13:47 +00:00
2013-03-18 13:52:27 +00:00
//report errors
2018-07-05 09:50:03 +00:00
if ( errors . Count > 0 )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
foreach ( string e in errors )
2016-02-22 08:04:06 +00:00
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + mde . ModelNames [ i ] + "\": " + e ) ;
2013-03-18 13:52:27 +00:00
}
}
}
2012-05-21 23:51:32 +00:00
2013-03-18 13:52:27 +00:00
//clear unneeded data
2016-07-18 12:05:19 +00:00
mde . SkinNames = null ;
2013-03-18 13:52:27 +00:00
mde . ModelNames = null ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
if ( mde . Model . Meshes = = null | | mde . Model . Meshes . Count = = 0 )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
mde . Model = null ;
return ;
}
2012-04-17 19:13:47 +00:00
2015-06-22 19:52:23 +00:00
//scale bbs
bbs . MaxX = ( int ) ( bbs . MaxX * mde . Scale . X ) ;
bbs . MinX = ( int ) ( bbs . MinX * mde . Scale . X ) ;
bbs . MaxY = ( int ) ( bbs . MaxY * mde . Scale . Y ) ;
bbs . MinY = ( int ) ( bbs . MinY * mde . Scale . Y ) ;
2020-01-03 01:22:33 +00:00
bbs . MaxZ = ( int ) ( bbs . MaxZ * mde . Scale . Z ) ;
bbs . MinZ = ( int ) ( bbs . MinZ * mde . Scale . Z ) ;
2015-06-22 19:52:23 +00:00
//calculate model radius
2018-07-05 09:50:03 +00:00
mde . Model . Radius = Math . Max ( Math . Max ( Math . Abs ( bbs . MinY ) , Math . Abs ( bbs . MaxY ) ) , Math . Max ( Math . Abs ( bbs . MinX ) , Math . Abs ( bbs . MaxX ) ) ) ;
2020-01-03 01:22:33 +00:00
mde . Model . BBox = bbs ;
2013-08-08 11:04:13 +00:00
}
2019-08-08 02:07:00 +00:00
private static Texture GetTexture ( List < DataReader > containers , string texturename )
2019-06-15 15:44:02 +00:00
{
Texture t = null ;
string [ ] extensions = new string [ ModelData . SUPPORTED_TEXTURE_EXTENSIONS . Length + 1 ] ;
Array . Copy ( ModelData . SUPPORTED_TEXTURE_EXTENSIONS , 0 , extensions , 1 , ModelData . SUPPORTED_TEXTURE_EXTENSIONS . Length ) ;
extensions [ 0 ] = "" ;
// Try to load the texture as defined by its path. GZDoom doesn't care about extensions
if ( t = = null )
{
foreach ( string extension in extensions )
{
string name = Path . ChangeExtension ( texturename , null ) + extension ;
name = name . Replace ( Path . AltDirectorySeparatorChar , Path . DirectorySeparatorChar ) ;
2019-08-08 02:07:00 +00:00
t = LoadTexture ( containers , name ) ;
2019-06-15 15:44:02 +00:00
if ( t ! = null )
break ;
}
}
// Try to use an already defined texture. Again, just try out all extensions
foreach ( string extension in extensions )
{
string name = Path . ChangeExtension ( texturename , null ) + extension ;
if ( General . Map . Data . GetTextureExists ( name ) )
{
ImageData image = General . Map . Data . GetTextureImage ( name ) ;
2021-06-26 20:20:39 +00:00
image . LoadImageNow ( false ) ;
2019-06-15 15:44:02 +00:00
t = image . Texture ;
break ;
}
}
// GZDoom can also ignore the path completely (because why not), so let's see if there's a texture with
// just the skin name
if ( t = = null )
{
string name = Path . ChangeExtension ( Path . GetFileName ( texturename ) , null ) ;
if ( General . Map . Data . GetTextureExists ( name ) )
{
ImageData image = General . Map . Data . GetTextureImage ( name ) ;
2021-06-26 20:20:39 +00:00
image . LoadImageNow ( false ) ;
2019-06-15 15:44:02 +00:00
t = image . Texture ;
}
}
// Or maybe it's a sprite
if ( t = = null )
{
string name = Path . ChangeExtension ( texturename , null ) ;
if ( General . Map . Data . GetSpriteExists ( name ) )
{
ImageData image = General . Map . Data . GetSpriteImage ( name ) ;
2021-06-26 20:20:39 +00:00
image . LoadImageNow ( false ) ;
2019-06-15 15:44:02 +00:00
t = image . Texture ;
}
}
return t ;
}
2018-05-27 05:53:54 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = 3D ( unreal )
// there is probably better way to emulate 16-bit cast, but this was easiest for me at 3am
private static int PadInt16 ( int n )
{
if ( n > 32767 )
return - ( 65536 - n ) ;
return n ;
}
private static float UnpackUVertex ( int n , int c )
{
switch ( c )
{
2018-07-05 09:50:03 +00:00
case 0 :
2018-06-07 08:59:52 +00:00
return PadInt16 ( ( n & 0x7ff ) < < 5 ) / 128f ;
2018-05-27 05:53:54 +00:00
case 1 :
2018-06-07 08:59:52 +00:00
return PadInt16 ( ( ( ( int ) n > > 11 ) & 0x7ff ) < < 5 ) / 128f ;
2018-07-05 09:50:03 +00:00
case 2 :
2018-06-07 08:59:52 +00:00
return PadInt16 ( ( ( ( int ) n > > 22 ) & 0x3ff ) < < 6 ) / 128f ;
2018-05-27 05:53:54 +00:00
default :
return 0f ;
}
}
private struct UE1Poly
{
public int [ ] V ;
public float [ ] S ;
public float [ ] T ;
2018-07-05 09:50:03 +00:00
public int TexNum , Type ;
public Vector3D Normal ;
2018-05-27 05:53:54 +00:00
}
2019-08-08 01:19:11 +00:00
internal static MD3LoadResult Read3DModel ( ref BoundingBoxSizes bbs , Dictionary < int , string > skins , Stream s , int frame , string filename , List < DataReader > containers )
2018-05-27 05:53:54 +00:00
{
Stream stream_d ;
Stream stream_a ;
if ( filename . IndexOf ( "_d.3d" ) = = filename . Length - 5 )
{
string filename_a = filename . Replace ( "_d.3d" , "_a.3d" ) ;
stream_d = s ;
stream_a = LoadFile ( containers , filename_a , true ) ;
if ( stream_a = = null )
{
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + filename + "\": unable to find corresponding \"_a.3d\" file." ) ;
return null ;
}
}
else
{
string filename_d = filename . Replace ( "_a.3d" , "_d.3d" ) ;
stream_a = s ;
stream_d = LoadFile ( containers , filename_d , true ) ;
if ( stream_d = = null )
{
General . ErrorLogger . Add ( ErrorType . Error , "Error while loading \"" + filename + "\": unable to find corresponding \"_d.3d\" file." ) ;
return null ;
}
}
MD3LoadResult result = new MD3LoadResult ( ) ;
BinaryReader br_d = new BinaryReader ( stream_d ) ;
BinaryReader br_a = new BinaryReader ( stream_a ) ;
// read d3d header
uint d3d_numpolys = br_d . ReadUInt16 ( ) ;
uint d3d_numverts = br_d . ReadUInt16 ( ) ;
2018-07-05 09:50:03 +00:00
stream_d . Position + = 44 ; // bogusrot, bogusframe, bogusnorm[3], fixscale, unused[3], padding[12]
2018-05-27 05:53:54 +00:00
long start_d = stream_d . Position ;
// read a3d header
uint a3d_numframes = br_a . ReadUInt16 ( ) ;
uint a3d_framesize = br_a . ReadUInt16 ( ) ;
long start_a = stream_a . Position ;
// Sanity check
if ( frame < 0 | | frame > = a3d_numframes )
{
result . Errors = "frame " + frame + " is outside of model's frame range [0.." + ( a3d_numframes - 1 ) + "]" ;
return result ;
}
2018-07-05 09:50:03 +00:00
// check for deus ex format
bool isdeusex = false ;
if ( ( a3d_framesize / d3d_numverts ) = = 8 ) isdeusex = true ;
2018-05-27 05:53:54 +00:00
// read vertices
WorldVertex [ ] vertices = new WorldVertex [ d3d_numverts ] ;
for ( uint i = 0 ; i < d3d_numverts ; i + + )
{
WorldVertex Vert = new WorldVertex ( ) ;
2018-07-05 09:50:03 +00:00
if ( isdeusex )
{
stream_a . Position = start_a + ( i + frame * d3d_numverts ) * 8 ;
int vx = br_a . ReadInt16 ( ) ;
int vy = br_a . ReadInt16 ( ) ;
int vz = br_a . ReadInt16 ( ) ;
Vert . y = - vx ;
Vert . z = vz ;
Vert . x = - vy ;
}
else
{
stream_a . Position = start_a + ( i + frame * d3d_numverts ) * 4 ;
int v_uint = br_a . ReadInt32 ( ) ;
Vert . y = - UnpackUVertex ( v_uint , 0 ) ;
Vert . z = UnpackUVertex ( v_uint , 2 ) ;
Vert . x = - UnpackUVertex ( v_uint , 1 ) ;
}
2018-05-27 05:53:54 +00:00
vertices [ i ] = Vert ;
}
// read polygons
2019-01-19 08:02:54 +00:00
//int minverthack = 0;
//int minvert = 2147483647;
2018-05-27 05:53:54 +00:00
UE1Poly [ ] polys = new UE1Poly [ d3d_numpolys ] ;
int [ ] polyindexlist = new int [ d3d_numpolys * 3 ] ;
for ( uint i = 0 ; i < d3d_numpolys ; i + + )
{
2018-07-05 09:50:03 +00:00
//
2018-05-27 05:53:54 +00:00
stream_d . Position = start_d + 16 * i ;
polys [ i ] . V = new int [ 3 ] ;
polys [ i ] . S = new float [ 3 ] ;
polys [ i ] . T = new float [ 3 ] ;
2018-07-25 13:03:41 +00:00
bool brokenpoly = false ;
2018-05-27 05:53:54 +00:00
for ( int j = 0 ; j < 3 ; j + + )
2018-07-25 13:03:41 +00:00
{
polyindexlist [ i * 3 + j ] = polys [ i ] . V [ j ] = br_d . ReadInt16 ( ) ;
if ( polys [ i ] . V [ j ] > = vertices . Length | | polys [ i ] . V [ j ] < 0 )
brokenpoly = true ;
}
// Resolves polygons that reference out-of-bounds vertices by simply making them null size.
// This is easier than changing array to dynamically sized list.
if ( brokenpoly )
{
polys [ i ] . V [ 0 ] = 0 ;
polys [ i ] . V [ 1 ] = 0 ;
polys [ i ] . V [ 2 ] = 0 ;
}
2018-07-05 09:50:03 +00:00
polys [ i ] . Type = br_d . ReadByte ( ) ;
stream_d . Position + = 1 ; // color
2018-05-27 05:53:54 +00:00
for ( int j = 0 ; j < 3 ; j + + )
{
byte u = br_d . ReadByte ( ) ;
byte v = br_d . ReadByte ( ) ;
polys [ i ] . S [ j ] = u / 255f ;
polys [ i ] . T [ j ] = v / 255f ;
}
polys [ i ] . TexNum = br_d . ReadByte ( ) ;
}
2018-07-05 09:50:03 +00:00
// calculate poly normals
for ( uint i = 0 ; i < d3d_numpolys ; i + + )
{
Vector3D [ ] dir = new Vector3D [ 2 ] ;
Vector3D norm ;
dir [ 0 ] . x = vertices [ polys [ i ] . V [ 1 ] ] . x - vertices [ polys [ i ] . V [ 0 ] ] . x ;
dir [ 0 ] . y = vertices [ polys [ i ] . V [ 1 ] ] . y - vertices [ polys [ i ] . V [ 0 ] ] . y ;
dir [ 0 ] . z = vertices [ polys [ i ] . V [ 1 ] ] . z - vertices [ polys [ i ] . V [ 0 ] ] . z ;
dir [ 1 ] . x = vertices [ polys [ i ] . V [ 2 ] ] . x - vertices [ polys [ i ] . V [ 0 ] ] . x ;
dir [ 1 ] . y = vertices [ polys [ i ] . V [ 2 ] ] . y - vertices [ polys [ i ] . V [ 0 ] ] . y ;
dir [ 1 ] . z = vertices [ polys [ i ] . V [ 2 ] ] . z - vertices [ polys [ i ] . V [ 0 ] ] . z ;
norm . x = dir [ 0 ] . y * dir [ 1 ] . z - dir [ 0 ] . z * dir [ 1 ] . y ;
norm . y = dir [ 0 ] . z * dir [ 1 ] . x - dir [ 0 ] . x * dir [ 1 ] . z ;
norm . z = dir [ 0 ] . x * dir [ 1 ] . y - dir [ 0 ] . y * dir [ 1 ] . x ;
polys [ i ] . Normal = norm . GetNormal ( ) ;
}
// calculate vertex normals
2018-05-27 05:53:54 +00:00
for ( uint i = 0 ; i < d3d_numverts ; i + + )
{
Vector3D nsum = new Vector3D ( 0 , 0 , 0 ) ;
int total = 0 ;
for ( uint j = 0 ; j < d3d_numpolys ; j + + )
{
if ( ( polys [ j ] . V [ 0 ] ! = i ) & & ( polys [ j ] . V [ 1 ] ! = i ) & & ( polys [ j ] . V [ 2 ] ! = i ) ) continue ;
2018-07-05 09:50:03 +00:00
nsum . x + = polys [ j ] . Normal . x ;
nsum . y + = polys [ j ] . Normal . y ;
nsum . z + = polys [ j ] . Normal . z ;
2018-05-27 05:53:54 +00:00
total + + ;
}
2020-05-21 12:20:02 +00:00
vertices [ i ] . nx = ( float ) - nsum . x / total ;
vertices [ i ] . ny = ( float ) - nsum . y / total ;
vertices [ i ] . nz = ( float ) - nsum . z / total ;
2018-05-27 05:53:54 +00:00
}
2018-05-27 20:25:17 +00:00
List < int > exGroups = new List < int > ( ) ;
2018-05-27 18:05:57 +00:00
Dictionary < int , int > textureGroupRemap = new Dictionary < int , int > ( ) ;
for ( int i = 0 ; i < polys . Length ; i + + )
{
2018-05-27 20:25:17 +00:00
if ( exGroups . Contains ( polys [ i ] . TexNum ) )
2018-05-27 18:05:57 +00:00
continue ;
2018-05-27 20:25:17 +00:00
if ( exGroups . Count = = 0 | |
polys [ i ] . TexNum < = exGroups [ 0 ] )
exGroups . Insert ( 0 , polys [ i ] . TexNum ) ;
else if ( exGroups . Count = = 0 | |
polys [ i ] . TexNum > = exGroups [ exGroups . Count - 1 ] )
exGroups . Add ( polys [ i ] . TexNum ) ;
2018-05-27 18:05:57 +00:00
}
2018-05-27 20:25:17 +00:00
for ( int i = 0 ; i < exGroups . Count ; i + + )
textureGroupRemap [ exGroups [ i ] ] = i ;
2018-05-27 18:05:57 +00:00
if ( skins = = null )
{
List < WorldVertex > out_verts = new List < WorldVertex > ( ) ;
List < int > out_polys = new List < int > ( ) ;
2018-05-27 05:53:54 +00:00
2018-05-27 18:05:57 +00:00
for ( int i = 0 ; i < polys . Length ; i + + )
{
2018-07-13 05:20:45 +00:00
if ( ( polys [ i ] . Type & 0x08 ) ! = 0 )
2018-07-05 09:50:03 +00:00
continue ;
2018-05-27 18:05:57 +00:00
for ( int j = 0 ; j < 3 ; j + + )
{
WorldVertex vx = vertices [ polys [ i ] . V [ j ] ] ;
vx . u = polys [ i ] . S [ j ] ;
vx . v = polys [ i ] . T [ j ] ;
2018-07-13 05:20:45 +00:00
if ( ( polys [ i ] . Type & 0x20 ) ! = 0 )
2018-07-05 09:50:03 +00:00
{
2020-05-21 12:20:02 +00:00
vx . nx = ( float ) polys [ i ] . Normal . x ;
vx . ny = ( float ) polys [ i ] . Normal . y ;
vx . nz = ( float ) polys [ i ] . Normal . z ;
2018-07-05 09:50:03 +00:00
}
2018-05-27 18:05:57 +00:00
out_polys . Add ( out_verts . Count ) ;
out_verts . Add ( vx ) ;
}
}
2018-05-27 05:53:54 +00:00
2019-08-08 01:19:11 +00:00
CreateMesh ( ref result , out_verts , out_polys ) ;
2018-05-27 18:05:57 +00:00
result . Skins . Add ( "" ) ;
}
else
2018-05-27 05:53:54 +00:00
{
2018-05-27 20:25:17 +00:00
for ( int k = 0 ; k < exGroups . Count ; k + + )
2018-05-27 05:53:54 +00:00
{
2018-05-27 18:05:57 +00:00
List < WorldVertex > out_verts = new List < WorldVertex > ( ) ;
List < int > out_polys = new List < int > ( ) ;
for ( int i = 0 ; i < polys . Length ; i + + )
{
2018-07-13 05:20:45 +00:00
if ( ( polys [ i ] . Type & 0x08 ) ! = 0 )
2018-07-05 09:50:03 +00:00
continue ;
2018-05-27 18:05:57 +00:00
if ( textureGroupRemap [ polys [ i ] . TexNum ] ! = k )
continue ;
for ( int j = 0 ; j < 3 ; j + + )
{
WorldVertex vx = vertices [ polys [ i ] . V [ j ] ] ;
vx . u = polys [ i ] . S [ j ] ;
vx . v = polys [ i ] . T [ j ] ;
2018-07-13 05:20:45 +00:00
if ( ( polys [ i ] . Type & 0x20 ) ! = 0 )
2018-07-05 09:50:03 +00:00
{
2020-05-21 12:20:02 +00:00
vx . nx = ( float ) polys [ i ] . Normal . x ;
vx . ny = ( float ) polys [ i ] . Normal . y ;
vx . nz = ( float ) polys [ i ] . Normal . z ;
2018-07-05 09:50:03 +00:00
}
2018-05-27 18:05:57 +00:00
out_polys . Add ( out_verts . Count ) ;
out_verts . Add ( vx ) ;
}
}
2019-08-08 01:19:11 +00:00
CreateMesh ( ref result , out_verts , out_polys ) ;
2018-05-27 18:05:57 +00:00
result . Skins . Add ( skins . ContainsKey ( k ) ? skins [ k ] . ToLowerInvariant ( ) : string . Empty ) ;
2018-05-27 05:53:54 +00:00
}
}
return result ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = MD3
2019-08-08 01:19:11 +00:00
internal static MD3LoadResult ReadMD3Model ( ref BoundingBoxSizes bbs , Dictionary < int , string > skins , Stream s , int frame )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
long start = s . Position ;
2013-03-18 13:52:27 +00:00
MD3LoadResult result = new MD3LoadResult ( ) ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
using ( var br = new BinaryReader ( s , Encoding . ASCII ) )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
string magic = ReadString ( br , 4 ) ;
2015-11-17 12:22:49 +00:00
if ( magic ! = "IDP3" )
{
2016-02-22 08:04:06 +00:00
result . Errors = "unknown header: expected \"IDP3\", but got \"" + magic + "\"" ;
2015-11-17 12:22:49 +00:00
return result ;
}
int modelVersion = br . ReadInt32 ( ) ;
if ( modelVersion ! = 15 ) //MD3 version. Must be equal to 15
2014-12-03 23:15:26 +00:00
{
2015-11-17 12:22:49 +00:00
result . Errors = "expected MD3 version 15, but got " + modelVersion ;
2013-03-18 13:52:27 +00:00
return result ;
}
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
s . Position + = 76 ;
2013-09-11 09:47:53 +00:00
int numSurfaces = br . ReadInt32 ( ) ;
s . Position + = 12 ;
int ofsSurfaces = br . ReadInt32 ( ) ;
2012-04-17 19:13:47 +00:00
Model rendering (all modes): UDMF scale, pitch and roll are now displayed.
Thing Edit Form, UDMF: added controls for setting pitch, roll, scale, render style, fill color, alpha, health and score.
Visual mode, UDMF: UDMF scale is now applied when rendering sprites.
Added Thing Statistics form (Edit -> View Thing Types...), which shows all loaded thing types with some additional info.
Visual mode: sprites with negative ScaleX and positive ScaleY were not rendered properly.
Classic modes: display was not updated after loading a sprite.
Current testing engine change was not saved on closing the program when no other game configuration settings were changed.
2014-04-30 10:01:22 +00:00
s . Position = ofsSurfaces + start ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
List < int > polyIndecesList = new List < int > ( ) ;
List < WorldVertex > vertList = new List < WorldVertex > ( ) ;
2012-04-17 19:13:47 +00:00
2014-02-26 14:11:06 +00:00
Dictionary < string , List < List < int > > > polyIndecesListsPerTexture = new Dictionary < string , List < List < int > > > ( StringComparer . Ordinal ) ;
Dictionary < string , List < WorldVertex > > vertListsPerTexture = new Dictionary < string , List < WorldVertex > > ( StringComparer . Ordinal ) ;
Dictionary < string , List < int > > vertexOffsets = new Dictionary < string , List < int > > ( StringComparer . Ordinal ) ;
2016-07-18 12:05:19 +00:00
bool useskins = false ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
for ( int c = 0 ; c < numSurfaces ; c + + )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
string skin = "" ;
2015-11-17 12:22:49 +00:00
string error = ReadSurface ( ref bbs , ref skin , br , polyIndecesList , vertList , frame ) ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
if ( ! string . IsNullOrEmpty ( error ) )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
result . Errors = error ;
return result ;
}
2012-04-17 19:13:47 +00:00
2016-07-18 12:05:19 +00:00
// Pick a skin to use
if ( skins = = null )
2014-12-03 23:15:26 +00:00
{
2016-07-18 12:05:19 +00:00
// skins is null when Skin MODELDEF property is set
skin = string . Empty ;
}
else if ( skins . ContainsKey ( c ) )
{
// Overrtide surface skin with SurfaceSkin MODELDEF property
skin = skins [ c ] ;
}
if ( ! string . IsNullOrEmpty ( skin ) )
{
useskins = true ;
2018-07-05 09:50:03 +00:00
if ( polyIndecesListsPerTexture . ContainsKey ( skin ) )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
polyIndecesListsPerTexture [ skin ] . Add ( polyIndecesList ) ;
vertListsPerTexture [ skin ] . AddRange ( vertList . ToArray ( ) ) ;
vertexOffsets [ skin ] . Add ( vertList . Count ) ;
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
polyIndecesListsPerTexture . Add ( skin , new List < List < int > > { polyIndecesList } ) ;
2013-03-18 13:52:27 +00:00
vertListsPerTexture . Add ( skin , vertList ) ;
2014-12-03 23:15:26 +00:00
vertexOffsets . Add ( skin , new List < int > { vertList . Count } ) ;
2013-03-18 13:52:27 +00:00
}
//reset lists
polyIndecesList = new List < int > ( ) ;
vertList = new List < WorldVertex > ( ) ;
}
2013-09-11 09:47:53 +00:00
}
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
if ( ! useskins )
2016-07-18 12:05:19 +00:00
{
2014-12-03 23:15:26 +00:00
//create mesh
2019-08-08 01:19:11 +00:00
CreateMesh ( ref result , vertList , polyIndecesList ) ;
2013-03-18 13:52:27 +00:00
result . Skins . Add ( "" ) ;
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
//create a mesh for each surface texture
2018-07-05 09:50:03 +00:00
foreach ( KeyValuePair < string , List < List < int > > > group in polyIndecesListsPerTexture )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
polyIndecesList = new List < int > ( ) ;
2013-07-15 08:32:32 +00:00
int offset = 0 ;
2018-07-05 09:50:03 +00:00
2013-03-18 13:52:27 +00:00
//collect indices, fix vertex offsets
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < group . Value . Count ; i + + )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
if ( i > 0 )
2014-12-03 23:15:26 +00:00
{
//TODO: Damn I need to rewrite all of this stuff from scratch...
2018-07-05 09:50:03 +00:00
offset + = vertexOffsets [ group . Key ] [ i - 1 ] ;
2013-07-15 08:32:32 +00:00
for ( int c = 0 ; c < group . Value [ i ] . Count ; c + + )
group . Value [ i ] [ c ] + = offset ;
2013-03-18 13:52:27 +00:00
}
polyIndecesList . AddRange ( group . Value [ i ] . ToArray ( ) ) ;
}
2019-08-08 01:19:11 +00:00
CreateMesh ( ref result , vertListsPerTexture [ group . Key ] , polyIndecesList ) ;
2013-03-18 13:52:27 +00:00
result . Skins . Add ( group . Key . ToLowerInvariant ( ) ) ;
}
}
2013-09-11 09:47:53 +00:00
}
return result ;
}
2018-07-05 09:50:03 +00:00
private static string ReadSurface ( ref BoundingBoxSizes bbs , ref string skin , BinaryReader br , List < int > polyIndecesList , List < WorldVertex > vertList , int frame )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
int vertexOffset = vertList . Count ;
long start = br . BaseStream . Position ;
2018-07-05 09:50:03 +00:00
2013-09-11 09:47:53 +00:00
string magic = ReadString ( br , 4 ) ;
2016-02-22 08:04:06 +00:00
if ( magic ! = "IDP3" ) return "error while reading surface. Unknown header: expected \"IDP3\", but got \"" + magic + "\"" ;
2013-09-11 09:47:53 +00:00
2015-11-17 12:22:49 +00:00
string name = ReadString ( br , 64 ) ;
int flags = br . ReadInt32 ( ) ;
int numFrames = br . ReadInt32 ( ) ; //Number of animation frames. This should match NUM_FRAMES in the MD3 header.
int numShaders = br . ReadInt32 ( ) ; //Number of Shader objects defined in this Surface, with a limit of MD3_MAX_SHADERS. Current value of MD3_MAX_SHADERS is 256.
2013-09-11 09:47:53 +00:00
int numVerts = br . ReadInt32 ( ) ; //Number of Vertex objects defined in this Surface, up to MD3_MAX_VERTS. Current value of MD3_MAX_VERTS is 4096.
int numTriangles = br . ReadInt32 ( ) ; //Number of Triangle objects defined in this Surface, maximum of MD3_MAX_TRIANGLES. Current value of MD3_MAX_TRIANGLES is 8192.
int ofsTriangles = br . ReadInt32 ( ) ; //Relative offset from SURFACE_START where the list of Triangle objects starts.
2013-03-18 13:52:27 +00:00
int ofsShaders = br . ReadInt32 ( ) ;
2013-09-11 09:47:53 +00:00
int ofsST = br . ReadInt32 ( ) ; //Relative offset from SURFACE_START where the list of ST objects (s-t texture coordinates) starts.
int ofsNormal = br . ReadInt32 ( ) ; //Relative offset from SURFACE_START where the list of Vertex objects (X-Y-Z-N vertices) starts.
int ofsEnd = br . ReadInt32 ( ) ; //Relative offset from SURFACE_START to where the Surface object ends.
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Sanity check
Sectors, Linedefs, Things modes: optimized text label rendering.
Fixed, Things mode: in some cases selection labels were not updated after editing a thing.
Fixed, Things mode: selection labels were positioned incorrectly on things with FixedSize setting.
Fixed, Sectors mode: fixed a crash when selecting self-referencing sector when selection labels were enabled.
Fixed, Visual mode: in some cases Auto-align texture actions were not working when "use long texture names" Map Options setting was enabled.
Fixed, MD2/MD3 loader: available animation frames upper bound check was performed incorrectly, which would cause a crash in some very special cases.
Fixed, Game configurations: most Hexen/ZDoom teleport actions use TeleportDests as teleport targets, not MapSpots.
2016-04-05 22:24:36 +00:00
if ( frame < 0 | | frame > = numFrames )
2015-11-17 12:22:49 +00:00
{
Sectors, Linedefs, Things modes: optimized text label rendering.
Fixed, Things mode: in some cases selection labels were not updated after editing a thing.
Fixed, Things mode: selection labels were positioned incorrectly on things with FixedSize setting.
Fixed, Sectors mode: fixed a crash when selecting self-referencing sector when selection labels were enabled.
Fixed, Visual mode: in some cases Auto-align texture actions were not working when "use long texture names" Map Options setting was enabled.
Fixed, MD2/MD3 loader: available animation frames upper bound check was performed incorrectly, which would cause a crash in some very special cases.
Fixed, Game configurations: most Hexen/ZDoom teleport actions use TeleportDests as teleport targets, not MapSpots.
2016-04-05 22:24:36 +00:00
return "frame " + frame + " is outside of model's frame range [0.." + ( numFrames - 1 ) + "]" ;
2015-11-17 12:22:49 +00:00
}
// Polygons
if ( start + ofsTriangles ! = br . BaseStream . Position )
2013-09-11 09:47:53 +00:00
br . BaseStream . Position = start + ofsTriangles ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
for ( int i = 0 ; i < numTriangles * 3 ; i + + )
polyIndecesList . Add ( vertexOffset + br . ReadInt32 ( ) ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Shaders
2013-03-18 13:52:27 +00:00
if ( start + ofsShaders ! = br . BaseStream . Position )
br . BaseStream . Position = start + ofsShaders ;
skin = ReadString ( br , 64 ) ; //we are interested only in the first one
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Vertices
if ( start + ofsST ! = br . BaseStream . Position )
2013-09-11 09:47:53 +00:00
br . BaseStream . Position = start + ofsST ;
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < numVerts ; i + + )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
WorldVertex v = new WorldVertex ( ) ;
v . c = - 1 ; //white
v . u = br . ReadSingle ( ) ;
v . v = br . ReadSingle ( ) ;
vertList . Add ( v ) ;
}
2015-11-17 12:22:49 +00:00
// Positions and normals
long vertoffset = start + ofsNormal + numVerts * 8 * frame ; // The length of Vertex struct is 8 bytes
if ( br . BaseStream . Position ! = vertoffset ) br . BaseStream . Position = vertoffset ;
2013-09-11 09:47:53 +00:00
2018-07-05 09:50:03 +00:00
for ( int i = vertexOffset ; i < vertexOffset + numVerts ; i + + )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
WorldVertex v = vertList [ i ] ;
//read vertex
v . y = - ( float ) br . ReadInt16 ( ) / 64 ;
v . x = ( float ) br . ReadInt16 ( ) / 64 ;
v . z = ( float ) br . ReadInt16 ( ) / 64 ;
//bounding box
BoundingBoxTools . UpdateBoundingBoxSizes ( ref bbs , v ) ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
var lat = br . ReadByte ( ) * ( 2 * Math . PI ) / 255.0 ;
var lng = br . ReadByte ( ) * ( 2 * Math . PI ) / 255.0 ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
v . nx = ( float ) ( Math . Sin ( lng ) * Math . Sin ( lat ) ) ;
v . ny = - ( float ) ( Math . Cos ( lng ) * Math . Sin ( lat ) ) ;
v . nz = ( float ) ( Math . Cos ( lat ) ) ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
vertList [ i ] = v ;
}
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
if ( start + ofsEnd ! = br . BaseStream . Position )
2013-09-11 09:47:53 +00:00
br . BaseStream . Position = start + ofsEnd ;
return "" ;
}
2012-04-17 19:13:47 +00:00
2013-08-08 11:04:13 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = MD2
2019-08-08 01:19:11 +00:00
private static MD3LoadResult ReadMD2Model ( ref BoundingBoxSizes bbs , Stream s , int frame , string framename )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
long start = s . Position ;
2013-03-18 13:52:27 +00:00
MD3LoadResult result = new MD3LoadResult ( ) ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
using ( var br = new BinaryReader ( s , Encoding . ASCII ) )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
string magic = ReadString ( br , 4 ) ;
2014-12-03 23:15:26 +00:00
if ( magic ! = "IDP2" ) //magic number: "IDP2"
2016-02-22 08:04:06 +00:00
{
result . Errors = "unknown header: expected \"IDP2\", but got \"" + magic + "\"" ;
2013-03-18 13:52:27 +00:00
return result ;
}
2012-05-21 23:51:32 +00:00
2013-09-11 09:47:53 +00:00
int modelVersion = br . ReadInt32 ( ) ;
2014-12-03 23:15:26 +00:00
if ( modelVersion ! = 8 ) //MD2 version. Must be equal to 8
2018-07-05 09:50:03 +00:00
{
2015-11-17 12:22:49 +00:00
result . Errors = "expected MD3 version 15, but got " + modelVersion ;
2013-03-18 13:52:27 +00:00
return result ;
}
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
int texWidth = br . ReadInt32 ( ) ;
int texHeight = br . ReadInt32 ( ) ;
2015-11-17 12:22:49 +00:00
int framesize = br . ReadInt32 ( ) ; // Size of one frame in bytes
s . Position + = 4 ; //Number of textures
2013-09-11 09:47:53 +00:00
int num_verts = br . ReadInt32 ( ) ; //Number of vertices
int num_uv = br . ReadInt32 ( ) ; //The number of UV coordinates in the model
int num_tris = br . ReadInt32 ( ) ; //Number of triangles
s . Position + = 4 ; //Number of OpenGL commands
2015-11-17 12:22:49 +00:00
int num_frames = br . ReadInt32 ( ) ; //Total number of frames
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Sanity checks
Sectors, Linedefs, Things modes: optimized text label rendering.
Fixed, Things mode: in some cases selection labels were not updated after editing a thing.
Fixed, Things mode: selection labels were positioned incorrectly on things with FixedSize setting.
Fixed, Sectors mode: fixed a crash when selecting self-referencing sector when selection labels were enabled.
Fixed, Visual mode: in some cases Auto-align texture actions were not working when "use long texture names" Map Options setting was enabled.
Fixed, MD2/MD3 loader: available animation frames upper bound check was performed incorrectly, which would cause a crash in some very special cases.
Fixed, Game configurations: most Hexen/ZDoom teleport actions use TeleportDests as teleport targets, not MapSpots.
2016-04-05 22:24:36 +00:00
if ( frame < 0 | | frame > = num_frames )
2015-11-17 12:22:49 +00:00
{
Sectors, Linedefs, Things modes: optimized text label rendering.
Fixed, Things mode: in some cases selection labels were not updated after editing a thing.
Fixed, Things mode: selection labels were positioned incorrectly on things with FixedSize setting.
Fixed, Sectors mode: fixed a crash when selecting self-referencing sector when selection labels were enabled.
Fixed, Visual mode: in some cases Auto-align texture actions were not working when "use long texture names" Map Options setting was enabled.
Fixed, MD2/MD3 loader: available animation frames upper bound check was performed incorrectly, which would cause a crash in some very special cases.
Fixed, Game configurations: most Hexen/ZDoom teleport actions use TeleportDests as teleport targets, not MapSpots.
2016-04-05 22:24:36 +00:00
result . Errors = "frame " + frame + " is outside of model's frame range [0.." + ( num_frames - 1 ) + "]" ;
2015-11-17 12:22:49 +00:00
return result ;
}
2013-09-11 09:47:53 +00:00
s . Position + = 4 ; //Offset to skin names (each skin name is an unsigned char[64] and are null terminated)
int ofs_uv = br . ReadInt32 ( ) ; //Offset to s-t texture coordinates
int ofs_tris = br . ReadInt32 ( ) ; //Offset to triangles
int ofs_animFrame = br . ReadInt32 ( ) ; //An offset to the first animation frame
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
List < int > polyIndecesList = new List < int > ( ) ;
List < int > uvIndecesList = new List < int > ( ) ;
2020-01-03 01:22:33 +00:00
List < Vector2f > uvCoordsList = new List < Vector2f > ( ) ;
2013-09-11 09:47:53 +00:00
List < WorldVertex > vertList = new List < WorldVertex > ( ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Polygons
Model rendering (all modes): UDMF scale, pitch and roll are now displayed.
Thing Edit Form, UDMF: added controls for setting pitch, roll, scale, render style, fill color, alpha, health and score.
Visual mode, UDMF: UDMF scale is now applied when rendering sprites.
Added Thing Statistics form (Edit -> View Thing Types...), which shows all loaded thing types with some additional info.
Visual mode: sprites with negative ScaleX and positive ScaleY were not rendered properly.
Classic modes: display was not updated after loading a sprite.
Current testing engine change was not saved on closing the program when no other game configuration settings were changed.
2014-04-30 10:01:22 +00:00
s . Position = ofs_tris + start ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < num_tris ; i + + )
2014-12-03 23:15:26 +00:00
{
2014-02-21 14:42:12 +00:00
polyIndecesList . Add ( br . ReadUInt16 ( ) ) ;
polyIndecesList . Add ( br . ReadUInt16 ( ) ) ;
polyIndecesList . Add ( br . ReadUInt16 ( ) ) ;
2012-04-17 19:13:47 +00:00
2014-02-21 14:42:12 +00:00
uvIndecesList . Add ( br . ReadUInt16 ( ) ) ;
uvIndecesList . Add ( br . ReadUInt16 ( ) ) ;
uvIndecesList . Add ( br . ReadUInt16 ( ) ) ;
2013-09-11 09:47:53 +00:00
}
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// UV coords
Model rendering (all modes): UDMF scale, pitch and roll are now displayed.
Thing Edit Form, UDMF: added controls for setting pitch, roll, scale, render style, fill color, alpha, health and score.
Visual mode, UDMF: UDMF scale is now applied when rendering sprites.
Added Thing Statistics form (Edit -> View Thing Types...), which shows all loaded thing types with some additional info.
Visual mode: sprites with negative ScaleX and positive ScaleY were not rendered properly.
Classic modes: display was not updated after loading a sprite.
Current testing engine change was not saved on closing the program when no other game configuration settings were changed.
2014-04-30 10:01:22 +00:00
s . Position = ofs_uv + start ;
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < num_uv ; i + + )
2020-01-03 01:22:33 +00:00
uvCoordsList . Add ( new Vector2f ( ( float ) br . ReadInt16 ( ) / texWidth , ( float ) br . ReadInt16 ( ) / texHeight ) ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
// Frames
// Find correct frame
if ( ! string . IsNullOrEmpty ( framename ) )
{
// Skip frames untill frame name matches
bool framefound = false ;
for ( int i = 0 ; i < num_frames ; i + + )
{
s . Position = ofs_animFrame + start + i * framesize ;
s . Position + = 24 ; // Skip scale and translate
string curframename = ReadString ( br , 16 ) . ToLowerInvariant ( ) ;
if ( curframename = = framename )
{
// Step back so scale and translate can be read
s . Position - = 40 ;
framefound = true ;
break ;
}
}
// No dice? Bail out!
if ( ! framefound )
{
2016-02-22 08:04:06 +00:00
result . Errors = "unable to find frame \"" + framename + "\"!" ;
2015-11-17 12:22:49 +00:00
return result ;
}
}
else
{
// If we have frame number, we can go directly to target frame
s . Position = ofs_animFrame + start + frame * framesize ;
}
2012-04-17 19:13:47 +00:00
2020-01-03 01:22:33 +00:00
Vector3f scale = new Vector3f ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ;
Vector3f translate = new Vector3f ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
s . Position + = 16 ; // Skip frame name
2012-04-17 19:13:47 +00:00
2014-12-10 22:50:16 +00:00
// Prepare to fix rotation angle
float angleOfsetCos = ( float ) Math . Cos ( - Angle2D . PIHALF ) ;
float angleOfsetSin = ( float ) Math . Sin ( - Angle2D . PIHALF ) ;
2013-09-11 09:47:53 +00:00
//verts
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < num_verts ; i + + )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
WorldVertex v = new WorldVertex ( ) ;
2014-02-21 14:42:12 +00:00
v . x = ( br . ReadByte ( ) * scale . X + translate . X ) ;
v . y = ( br . ReadByte ( ) * scale . Y + translate . Y ) ;
v . z = ( br . ReadByte ( ) * scale . Z + translate . Z ) ;
2013-09-11 09:47:53 +00:00
2014-12-10 22:50:16 +00:00
// Fix rotation angle
float rx = angleOfsetCos * v . x - angleOfsetSin * v . y ;
float ry = angleOfsetSin * v . x + angleOfsetCos * v . y ;
v . y = ry ;
v . x = rx ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
vertList . Add ( v ) ;
s . Position + = 1 ; //vertex normal
}
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < polyIndecesList . Count ; i + + )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
WorldVertex v = vertList [ polyIndecesList [ i ] ] ;
2018-07-05 09:50:03 +00:00
2013-09-11 09:47:53 +00:00
//bounding box
BoundingBoxTools . UpdateBoundingBoxSizes ( ref bbs , new WorldVertex ( v . y , v . x , v . z ) ) ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
//uv
2012-11-20 22:08:34 +00:00
float tu = uvCoordsList [ uvIndecesList [ i ] ] . X ;
2013-09-11 09:47:53 +00:00
float tv = uvCoordsList [ uvIndecesList [ i ] ] . Y ;
2012-11-20 22:08:34 +00:00
//uv-coordinates already set?
2018-07-05 09:50:03 +00:00
if ( v . c = = - 1 & & ( v . u ! = tu | | v . v ! = tv ) )
{
2012-11-20 22:08:34 +00:00
//add a new vertex
vertList . Add ( new WorldVertex ( v . x , v . y , v . z , - 1 , tu , tv ) ) ;
polyIndecesList [ i ] = vertList . Count - 1 ;
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
2012-11-20 22:08:34 +00:00
v . u = tu ;
v . v = tv ;
v . c = - 1 ; //set color to white
//return to proper place
vertList [ polyIndecesList [ i ] ] = v ;
}
2013-09-11 09:47:53 +00:00
}
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
//mesh
2019-08-16 09:24:22 +00:00
Mesh mesh = new Mesh ( General . Map . Graphics , vertList . ToArray ( ) , polyIndecesList . ToArray ( ) ) ;
2012-04-17 19:13:47 +00:00
2013-03-18 13:52:27 +00:00
//store in result
result . Meshes . Add ( mesh ) ;
result . Skins . Add ( "" ) ; //no skin support for MD2
2013-09-11 09:47:53 +00:00
}
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
return result ;
2013-08-08 11:04:13 +00:00
}
#endregion
2014-01-03 10:33:45 +00:00
#region = = = = = = = = = = = = = = = = = = KVX
2019-08-08 01:19:11 +00:00
private static void ReadKVX ( ModelData mde , Stream stream )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
PixelColor [ ] palette = new PixelColor [ 256 ] ;
List < WorldVertex > verts = new List < WorldVertex > ( ) ;
2016-07-18 23:41:02 +00:00
List < int > indices = new List < int > ( ) ;
Dictionary < long , int > verthashes = new Dictionary < long , int > ( ) ;
2014-01-03 10:33:45 +00:00
int xsize , ysize , zsize ;
2016-07-18 23:41:02 +00:00
int facescount = 0 ;
2014-01-03 10:33:45 +00:00
Vector3D pivot ;
2018-07-05 09:50:03 +00:00
using ( BinaryReader reader = new BinaryReader ( stream , Encoding . ASCII ) )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
reader . ReadInt32 ( ) ; //numbytes, we don't use that
xsize = reader . ReadInt32 ( ) ;
ysize = reader . ReadInt32 ( ) ;
zsize = reader . ReadInt32 ( ) ;
pivot = new Vector3D ( ) ;
pivot . x = reader . ReadInt32 ( ) / 256f ;
pivot . y = reader . ReadInt32 ( ) / 256f ;
pivot . z = 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?..
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < xoffset . Length ; i + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
xoffset [ i ] = reader . ReadInt32 ( ) ;
}
2018-07-05 09:50:03 +00:00
for ( int x = 0 ; x < xsize ; x + + )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
for ( int y = 0 ; y < ysize + 1 ; y + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
xyoffset [ x , y ] = reader . ReadInt16 ( ) ;
}
}
//read slabs
List < int > offsets = new List < int > ( xsize * ysize ) ;
2018-07-05 09:50:03 +00:00
for ( int x = 0 ; x < xsize ; x + + )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
for ( int y = 0 ; y < ysize ; y + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
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
2018-07-05 09:50:03 +00:00
if ( ! mde . OverridePalette )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
reader . BaseStream . Position = slabsEnd ;
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < 256 ; i + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
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 ) ;
}
2018-07-05 09:50:03 +00:00
}
else
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < 256 ; i + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
palette [ i ] = General . Map . Data . Palette [ i ] ;
}
}
2018-07-05 09:50:03 +00:00
for ( int x = 0 ; x < xsize ; x + + )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
for ( int y = 0 ; y < ysize ; y + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
reader . BaseStream . Position = offsets [ counter ] ;
int next = ( counter < offsets . Count - 1 ? offsets [ counter + 1 ] : slabsEnd ) ;
//read slab
2018-07-05 09:50:03 +00:00
while ( reader . BaseStream . Position < next )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
int ztop = reader . ReadByte ( ) ;
int zleng = reader . ReadByte ( ) ;
if ( ztop + zleng > zsize ) break ;
int flags = reader . ReadByte ( ) ;
2018-07-05 09:50:03 +00:00
if ( zleng > 0 )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
List < int > colorIndices = new List < int > ( zleng ) ;
2018-07-05 09:50:03 +00:00
for ( int i = 0 ; i < zleng ; i + + )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
colorIndices . Add ( reader . ReadByte ( ) ) ;
}
2018-07-05 09:50:03 +00:00
if ( ( flags & 16 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x , y , ztop ) , new Vector3D ( x + 1 , y , ztop ) , new Vector3D ( x , y + 1 , ztop ) , new Vector3D ( x + 1 , y + 1 , ztop ) , pivot , colorIndices [ 0 ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
int z = ztop ;
int cstart = 0 ;
2018-07-05 09:50:03 +00:00
while ( z < ztop + zleng )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
int c = 0 ;
while ( z + c < ztop + zleng & & colorIndices [ cstart + c ] = = colorIndices [ cstart ] ) c + + ;
2018-07-05 09:50:03 +00:00
if ( ( flags & 1 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x , y , z ) , new Vector3D ( x , y + 1 , z ) , new Vector3D ( x , y , z + c ) , new Vector3D ( x , y + 1 , z + c ) , pivot , colorIndices [ cstart ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
2018-07-05 09:50:03 +00:00
if ( ( flags & 2 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x + 1 , y + 1 , z ) , new Vector3D ( x + 1 , y , z ) , new Vector3D ( x + 1 , y + 1 , z + c ) , new Vector3D ( x + 1 , y , z + c ) , pivot , colorIndices [ cstart ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
2018-07-05 09:50:03 +00:00
if ( ( flags & 4 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x + 1 , y , z ) , new Vector3D ( x , y , z ) , new Vector3D ( x + 1 , y , z + c ) , new Vector3D ( x , y , z + c ) , pivot , colorIndices [ cstart ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
2018-07-05 09:50:03 +00:00
if ( ( flags & 8 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x , y + 1 , z ) , new Vector3D ( x + 1 , y + 1 , z ) , new Vector3D ( x , y + 1 , z + c ) , new Vector3D ( x + 1 , y + 1 , z + c ) , pivot , colorIndices [ cstart ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
if ( c = = 0 ) c + + ;
z + = c ;
cstart + = c ;
}
2018-07-05 09:50:03 +00:00
if ( ( flags & 32 ) ! = 0 )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
z = ztop + zleng - 1 ;
2016-07-18 23:41:02 +00:00
AddFace ( verts , indices , verthashes , new Vector3D ( x + 1 , y , z + 1 ) , new Vector3D ( x , y , z + 1 ) , new Vector3D ( x + 1 , y + 1 , z + 1 ) , new Vector3D ( x , y + 1 , z + 1 ) , pivot , colorIndices [ zleng - 1 ] ) ;
facescount + = 2 ;
2014-01-03 10:33:45 +00:00
}
}
}
counter + + ;
}
}
}
2015-06-22 19:52:23 +00:00
// get model extents
int minX = ( int ) ( ( xsize / 2f - pivot . x ) * mde . Scale . X ) ;
int maxX = ( int ) ( ( xsize / 2f + pivot . x ) * mde . Scale . X ) ;
int minY = ( int ) ( ( ysize / 2f - pivot . y ) * mde . Scale . Y ) ;
int maxY = ( int ) ( ( ysize / 2f + pivot . y ) * mde . Scale . Y ) ;
2018-07-05 09:50:03 +00:00
2016-07-18 23:41:02 +00:00
// Calculate model radius
2015-06-22 19:52:23 +00:00
mde . Model . Radius = Math . Max ( Math . Max ( Math . Abs ( minY ) , Math . Abs ( maxY ) ) , Math . Max ( Math . Abs ( minX ) , Math . Abs ( maxX ) ) ) ;
2014-01-03 10:33:45 +00:00
2019-08-09 21:15:48 +00:00
// Create texture new Texture(bmp.Width)
using ( Bitmap bmp = CreateVoxelTexture ( palette ) )
{
2019-08-15 00:52:21 +00:00
mde . Model . Textures . Add ( new Texture ( General . Map . Graphics , bmp ) ) ;
2019-08-09 21:15:48 +00:00
}
2014-01-03 10:33:45 +00:00
2016-07-18 23:41:02 +00:00
// Create mesh
2019-08-16 09:24:22 +00:00
Mesh mesh = new Mesh ( General . Map . Graphics , verts . ToArray ( ) , indices . ToArray ( ) ) ;
2014-01-03 10:33:45 +00:00
2016-07-18 23:41:02 +00:00
// Add mesh
2014-01-03 10:33:45 +00:00
mde . Model . Meshes . Add ( mesh ) ;
}
// Shameless GZDoom rip-off
2018-07-05 09:50:03 +00:00
private static void AddFace ( List < WorldVertex > verts , List < int > indices , Dictionary < long , int > hashes , Vector3D v1 , Vector3D v2 , Vector3D v3 , Vector3D v4 , Vector3D pivot , int colorIndex )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
float pu0 = ( colorIndex % 16 ) / 16f ;
2016-07-18 23:41:02 +00:00
float pu1 = pu0 + 0.001f ;
2014-01-03 10:33:45 +00:00
float pv0 = ( colorIndex / 16 ) / 16f ;
2016-07-18 23:41:02 +00:00
float pv1 = pv0 + 0.001f ;
2018-07-05 09:50:03 +00:00
2016-07-18 23:41:02 +00:00
WorldVertex wv1 = new WorldVertex
{
2020-05-21 12:20:02 +00:00
x = ( float ) ( v1 . x - pivot . x ) ,
y = ( float ) ( - v1 . y + pivot . y ) ,
z = ( float ) ( - v1 . z + pivot . z ) ,
2016-07-18 23:41:02 +00:00
c = - 1 ,
u = pu0 ,
v = pv0
} ;
int i1 = AddVertex ( wv1 , verts , indices , hashes ) ;
WorldVertex wv2 = new WorldVertex
{
2020-05-21 12:20:02 +00:00
x = ( float ) ( v2 . x - pivot . x ) ,
y = ( float ) ( - v2 . y + pivot . y ) ,
z = ( float ) ( - v2 . z + pivot . z ) ,
2016-07-18 23:41:02 +00:00
c = - 1 ,
u = pu1 ,
v = pv1
} ;
AddVertex ( wv2 , verts , indices , hashes ) ;
WorldVertex wv4 = new WorldVertex
{
2020-05-21 12:20:02 +00:00
x = ( float ) ( v4 . x - pivot . x ) ,
y = ( float ) ( - v4 . y + pivot . y ) ,
z = ( float ) ( - v4 . z + pivot . z ) ,
2016-07-18 23:41:02 +00:00
c = - 1 ,
u = pu0 ,
v = pv0
} ;
int i4 = AddVertex ( wv4 , verts , indices , hashes ) ;
WorldVertex wv3 = new WorldVertex
{
2020-05-21 12:20:02 +00:00
x = ( float ) ( v3 . x - pivot . x ) ,
y = ( float ) ( - v3 . y + pivot . y ) ,
z = ( float ) ( - v3 . z + pivot . z ) ,
2016-07-18 23:41:02 +00:00
c = - 1 ,
u = pu1 ,
v = pv1
} ;
AddVertex ( wv3 , verts , indices , hashes ) ;
indices . Add ( i1 ) ;
indices . Add ( i4 ) ;
}
// Returns index of added vert
private static int AddVertex ( WorldVertex v , List < WorldVertex > verts , List < int > indices , Dictionary < long , int > hashes )
{
long hash ;
unchecked // Overflow is fine, just wrap
{
hash = 2166136261 ;
hash = ( hash * 16777619 ) ^ v . x . GetHashCode ( ) ;
hash = ( hash * 16777619 ) ^ v . y . GetHashCode ( ) ;
hash = ( hash * 16777619 ) ^ v . z . GetHashCode ( ) ;
hash = ( hash * 16777619 ) ^ v . u . GetHashCode ( ) ;
hash = ( hash * 16777619 ) ^ v . v . GetHashCode ( ) ;
}
if ( hashes . ContainsKey ( hash ) )
{
indices . Add ( hashes [ hash ] ) ;
return hashes [ hash ] ;
}
else
{
verts . Add ( v ) ;
hashes . Add ( hash , verts . Count - 1 ) ;
indices . Add ( verts . Count - 1 ) ;
return verts . Count - 1 ;
}
2014-01-03 10:33:45 +00:00
}
2018-07-05 09:50:03 +00:00
private unsafe static Bitmap CreateVoxelTexture ( PixelColor [ ] palette )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
Bitmap bmp = new Bitmap ( 16 , 16 ) ;
BitmapData bmpdata = bmp . LockBits ( new Rectangle ( 0 , 0 , 16 , 16 ) , ImageLockMode . WriteOnly , PixelFormat . Format32bppArgb ) ;
2018-07-05 09:50:03 +00:00
if ( bmpdata ! = null )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
const int numpixels = 256 ;
int i = 255 ;
2018-07-05 09:50:03 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - , i - - )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
cp - > r = palette [ i ] . r ;
cp - > g = palette [ i ] . g ;
cp - > b = palette [ i ] . b ;
cp - > a = palette [ i ] . a ;
}
bmp . UnlockBits ( bmpdata ) ;
}
//scale bitmap, so colors stay (almost) the same when bilinear filtering is enabled
Bitmap scaled = new Bitmap ( 64 , 64 ) ;
2018-07-05 09:50:03 +00:00
using ( Graphics gs = Graphics . FromImage ( scaled ) )
2014-12-03 23:15:26 +00:00
{
2014-01-03 10:33:45 +00:00
gs . InterpolationMode = System . Drawing . Drawing2D . InterpolationMode . NearestNeighbor ;
gs . DrawImage ( bmp , new Rectangle ( 0 , 0 , 64 , 64 ) , new Rectangle ( 0 , 0 , 16 , 16 ) , GraphicsUnit . Pixel ) ;
}
2015-05-27 10:34:25 +00:00
bmp . Dispose ( ) ;
2014-01-03 10:33:45 +00:00
return scaled ;
}
#endregion
2019-06-15 15:44:02 +00:00
#region = = = = = = = = = = = = = = = = = = OBJ
2019-08-08 02:07:00 +00:00
private static MD3LoadResult ReadOBJModel ( ref BoundingBoxSizes bbs , Dictionary < int , string > skins , Stream s , string name )
2019-06-15 15:44:02 +00:00
{
MD3LoadResult result = new MD3LoadResult ( ) ;
using ( var reader = new StreamReader ( s , Encoding . ASCII ) )
{
string line ;
int linenum = 1 ;
string message ;
int surfaceskinid = 0 ;
List < Vector3D > vertices = new List < Vector3D > ( ) ;
List < int > faces = new List < int > ( ) ;
List < Vector3D > normals = new List < Vector3D > ( ) ;
List < Vector2D > texcoords = new List < Vector2D > ( ) ;
List < WorldVertex > worldvertices = new List < WorldVertex > ( ) ;
List < int > polyindiceslist = new List < int > ( ) ;
while ( ( line = reader . ReadLine ( ) ) ! = null ) {
string [ ] fields = line . Trim ( ) . Split ( new [ ] { ' ' , '\t' } , 2 , StringSplitOptions . RemoveEmptyEntries ) ;
2021-06-24 16:07:05 +00:00
// Empty line
if ( fields . Length = = 0 )
{
linenum + + ;
continue ;
}
// Comment
if ( fields [ 0 ] . Trim ( ) = = "#" )
{
linenum + + ;
continue ;
}
2019-06-15 15:44:02 +00:00
string keyword = fields [ 0 ] . Trim ( ) ;
2019-06-16 10:05:03 +00:00
string payload = null ;
if ( fields . Length = = 2 )
payload = fields [ 1 ] . Trim ( ) ;
2019-06-15 15:44:02 +00:00
switch ( keyword )
{
case "v" :
Vector3D v = new Vector3D ( 0 , 0 , 0 ) ;
if ( OBJParseVertex ( payload , ref v , out message ) )
vertices . Add ( v ) ;
else
{
result . Errors = String . Format ( "Error in line {0}: {1}" , linenum , message ) ;
return result ;
}
break ;
case "vt" :
Vector2D t = new Vector2D ( 0 , 0 ) ;
if ( OBJParseTextureCoords ( payload , ref t , out message ) )
texcoords . Add ( t ) ;
else
{
result . Errors = String . Format ( "Error in line {0}: {1}" , linenum , message ) ;
return result ;
}
break ;
case "vn" :
Vector3D n = new Vector3D ( 0 , 0 , 0 ) ;
if ( OBJParseNormal ( payload , ref n , out message ) )
normals . Add ( n ) ;
else
{
result . Errors = String . Format ( "Error in line {0}: {1}" , linenum , message ) ;
return result ;
}
break ;
case "f" :
List < int > fv = new List < int > ( ) ;
List < int > vt = new List < int > ( ) ;
List < int > vn = new List < int > ( ) ;
if ( OBJParseFace ( payload , ref fv , ref vt , ref vn , out message ) )
{
// Sanity check for vertices
for ( int i = 0 ; i < fv . Count ; i + + )
2021-06-24 16:07:05 +00:00
if ( fv [ i ] ! = - 1 & & fv [ i ] > = vertices . Count )
2019-06-15 15:44:02 +00:00
{
2021-06-24 16:07:05 +00:00
result . Errors = String . Format ( "Error in line {0}: vertex {1} does not exist" , linenum , fv [ i ] + 1 ) ;
2019-06-15 15:44:02 +00:00
return result ;
}
// Sanity check for texture coordinates
for ( int i = 0 ; i < vt . Count ; i + + )
2021-06-24 16:07:05 +00:00
if ( vt [ i ] ! = - 1 & & vt [ i ] > = texcoords . Count )
2019-06-15 15:44:02 +00:00
{
2021-06-24 16:07:05 +00:00
result . Errors = String . Format ( "Error in line {0}: texture coordinate {1} does not exist" , linenum , vt [ i ] + 1 ) ;
2019-06-15 15:44:02 +00:00
return result ;
}
// Sanity check for normals
for ( int i = 0 ; i < vn . Count ; i + + )
2021-06-24 16:07:05 +00:00
if ( vn [ i ] ! = - 1 & & vn [ i ] > = normals . Count )
2019-06-15 15:44:02 +00:00
{
2021-06-24 16:07:05 +00:00
result . Errors = String . Format ( "Error in line {0}: vertex {1} does not exist" , linenum , vn [ i ] + 1 ) ;
2019-06-15 15:44:02 +00:00
return result ;
}
int [ ] seq ;
// If the face is a quad split it into two triangles
if ( fv . Count = = 3 )
seq = new int [ ] { 0 , 1 , 2 } ;
else
seq = new int [ ] { 0 , 1 , 2 , 0 , 2 , 3 } ;
for ( int i = 0 ; i < seq . Length ; i + + ) {
WorldVertex wc = new WorldVertex ( vertices [ fv [ seq [ i ] ] ] ) ;
if ( vt [ seq [ i ] ] ! = - 1 )
{
2020-05-21 12:20:02 +00:00
wc . u = ( float ) texcoords [ vt [ seq [ i ] ] ] . x ;
wc . v = ( float ) texcoords [ vt [ seq [ i ] ] ] . y ;
2019-06-15 15:44:02 +00:00
}
if ( vn [ seq [ i ] ] ! = - 1 )
{
2020-05-21 12:20:02 +00:00
wc . nx = ( float ) normals [ vn [ seq [ i ] ] ] . x ;
wc . ny = ( float ) normals [ vn [ seq [ i ] ] ] . y ;
wc . nz = ( float ) normals [ vn [ seq [ i ] ] ] . z ;
2019-06-15 15:44:02 +00:00
}
BoundingBoxTools . UpdateBoundingBoxSizes ( ref bbs , wc ) ;
worldvertices . Add ( wc ) ;
polyindiceslist . Add ( polyindiceslist . Count ) ;
}
}
else
{
result . Errors = String . Format ( "Error in line {0}: {1}" , linenum , message ) ;
return result ;
}
break ;
case "usemtl" :
// If there's a new texture defined create a mesh from the current faces and
// start a gather new faces for the next mesh
if ( worldvertices . Count > 0 )
{
2019-08-08 02:07:00 +00:00
CreateMesh ( ref result , worldvertices , polyindiceslist ) ;
2019-06-15 15:44:02 +00:00
worldvertices . Clear ( ) ;
polyindiceslist . Clear ( ) ;
}
2022-07-13 21:25:10 +00:00
// Add texture name. It might be in quotes, so remove them.
// See https://github.com/jewalky/UltimateDoomBuilder/issues/758
2019-06-15 15:44:02 +00:00
if ( fields . Length > = 2 )
2022-07-13 21:25:10 +00:00
result . Skins . Add ( fields [ 1 ] . Replace ( "\"" , "" ) ) ;
2019-06-15 15:44:02 +00:00
surfaceskinid + + ;
break ;
case "" : // Empty line
case "#" : // Line is a comment
case "s" : // Smooth
2019-06-16 10:05:03 +00:00
case "g" : // Group
case "o" : // Object
2019-06-15 15:44:02 +00:00
default :
break ;
}
linenum + + ;
}
2019-08-08 02:07:00 +00:00
CreateMesh ( ref result , worldvertices , polyindiceslist ) ;
2019-06-15 15:44:02 +00:00
// Overwrite internal textures with SurfaceSkin definitions if necessary
if ( skins ! = null )
{
foreach ( KeyValuePair < int , string > group in skins )
{
// Add dummy skins if necessary
while ( result . Skins . Count < = group . Key )
result . Skins . Add ( String . Empty ) ;
result . Skins [ group . Key ] = group . Value ;
}
}
}
return result ;
}
private static bool OBJParseVertex ( string payload , ref Vector3D v , out string message )
{
2019-06-16 10:05:03 +00:00
if ( String . IsNullOrEmpty ( payload ) )
{
message = "no arguments given" ;
return false ;
}
2019-06-15 15:44:02 +00:00
string [ ] fields = payload . Split ( new [ ] { ' ' , '\t' } , StringSplitOptions . RemoveEmptyEntries ) ;
if ( fields . Length < 3 )
{
message = "too few arguments" ;
return false ;
}
try
{
v . x = float . Parse ( fields [ 0 ] , CultureInfo . InvariantCulture ) ;
v . z = float . Parse ( fields [ 1 ] , CultureInfo . InvariantCulture ) ;
2019-06-15 22:03:37 +00:00
v . y = - float . Parse ( fields [ 2 ] , CultureInfo . InvariantCulture ) ;
2019-06-15 15:44:02 +00:00
// Prepare to fix rotation angle
2020-05-21 12:20:02 +00:00
double angleOfsetCos = Math . Cos ( - Angle2D . PIHALF ) ;
double angleOfsetSin = Math . Sin ( - Angle2D . PIHALF ) ;
2019-06-15 15:44:02 +00:00
// Fix rotation angle
2020-05-21 12:20:02 +00:00
double rx = angleOfsetCos * v . x - angleOfsetSin * v . y ;
double ry = angleOfsetSin * v . x + angleOfsetCos * v . y ;
2019-06-15 15:44:02 +00:00
v . x = rx ;
v . y = ry ;
}
2019-06-15 19:26:43 +00:00
catch ( FormatException )
2019-06-15 15:44:02 +00:00
{
message = "field is not a float" ;
return false ;
}
message = "" ;
return true ;
}
private static bool OBJParseTextureCoords ( string payload , ref Vector2D t , out string message )
{
2019-06-16 10:05:03 +00:00
if ( String . IsNullOrEmpty ( payload ) )
{
message = "no arguments given" ;
return false ;
}
2019-06-15 15:44:02 +00:00
string [ ] fields = payload . Split ( new [ ] { ' ' , '\t' } , StringSplitOptions . RemoveEmptyEntries ) ;
if ( fields . Length < 2 )
{
message = "too few arguments" ;
return false ;
}
try
{
t . x = float . Parse ( fields [ 0 ] , CultureInfo . InvariantCulture ) ;
2022-09-03 10:25:26 +00:00
if ( fields . Length > = 2 )
2020-06-19 10:22:57 +00:00
t . y = 1.0f - float . Parse ( fields [ 1 ] , CultureInfo . InvariantCulture ) ;
2019-06-15 15:44:02 +00:00
else
2020-06-19 10:22:57 +00:00
t . y = 1.0f ;
2019-06-15 15:44:02 +00:00
}
2019-06-15 19:26:43 +00:00
catch ( FormatException )
2019-06-15 15:44:02 +00:00
{
message = "field is not a float" ;
return false ;
}
message = "" ;
return true ;
}
private static bool OBJParseNormal ( string payload , ref Vector3D normal , out string message )
{
2019-06-16 10:05:03 +00:00
if ( String . IsNullOrEmpty ( payload ) )
{
message = "no arguments given" ;
return false ;
}
2019-06-15 15:44:02 +00:00
string [ ] fields = payload . Split ( new [ ] { ' ' , '\t' } , StringSplitOptions . RemoveEmptyEntries ) ;
if ( fields . Length < 3 )
{
message = "too few arguments" ;
return false ;
}
try
{
normal . x = float . Parse ( fields [ 0 ] , CultureInfo . InvariantCulture ) ;
normal . y = float . Parse ( fields [ 1 ] , CultureInfo . InvariantCulture ) ;
normal . z = float . Parse ( fields [ 2 ] , CultureInfo . InvariantCulture ) ;
}
2019-06-15 19:26:43 +00:00
catch ( FormatException )
2019-06-15 15:44:02 +00:00
{
message = "field is not a float" ;
return false ;
}
message = "" ;
return true ;
}
private static bool OBJParseFace ( string payload , ref List < int > face , ref List < int > texcoords , ref List < int > normals , out string message )
{
2019-06-16 10:05:03 +00:00
if ( String . IsNullOrEmpty ( payload ) )
{
message = "no arguments given" ;
return false ;
}
2019-06-15 15:44:02 +00:00
string [ ] fields = payload . Split ( new [ ] { ' ' , '\t' } , StringSplitOptions . RemoveEmptyEntries ) ;
if ( fields . Length < 3 )
{
message = "too few arguments" ;
return false ;
}
if ( fields . Length > 4 )
{
message = "faces with more than 4 sides are not supported" ;
return false ;
}
try
{
for ( int i = 0 ; i < fields . Length ; i + + )
{
string [ ] vertexdata = fields [ i ] . Split ( '/' ) ;
face . Add ( int . Parse ( vertexdata [ 0 ] , CultureInfo . InvariantCulture ) - 1 ) ;
2019-08-08 10:17:49 +00:00
if ( vertexdata . Length > 1 & & vertexdata [ 1 ] ! = "" )
2019-06-15 15:44:02 +00:00
texcoords . Add ( int . Parse ( vertexdata [ 1 ] , CultureInfo . InvariantCulture ) - 1 ) ;
else
texcoords . Add ( - 1 ) ;
2019-08-08 10:17:49 +00:00
if ( vertexdata . Length > 2 & & vertexdata [ 2 ] ! = "" )
2019-06-15 15:44:02 +00:00
normals . Add ( int . Parse ( vertexdata [ 2 ] , CultureInfo . InvariantCulture ) - 1 ) ;
else
normals . Add ( - 1 ) ;
}
}
2019-06-15 19:26:43 +00:00
catch ( FormatException )
2019-06-15 15:44:02 +00:00
{
message = "field is not an integer" ;
return false ;
}
message = "" ;
return true ;
}
#endregion
2013-08-08 11:04:13 +00:00
#region = = = = = = = = = = = = = = = = = = Utility
2012-04-17 19:13:47 +00:00
2018-07-05 09:50:03 +00:00
private static MemoryStream LoadFile ( List < DataReader > containers , string path , bool isModel )
2014-12-03 23:15:26 +00:00
{
2018-07-05 09:50:03 +00:00
foreach ( DataReader dr in containers )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
if ( isModel & & dr is WADReader ) continue ; //models cannot be stored in WADs
//load file
2014-12-03 23:15:26 +00:00
if ( dr . FileExists ( path ) ) return dr . LoadFile ( path ) ;
2013-03-18 13:52:27 +00:00
}
return null ;
}
2019-08-08 01:19:11 +00:00
private static Texture LoadTexture ( List < DataReader > containers , string path )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
if ( string . IsNullOrEmpty ( path ) ) return null ;
2019-06-15 15:44:02 +00:00
MemoryStream ms = LoadFile ( containers , path , true ) ;
2013-03-18 13:52:27 +00:00
if ( ms = = null ) return null ;
Texture texture = null ;
2020-01-14 16:25:35 +00:00
//create texture
Bitmap bitmap = ImageDataFormat . TryLoadImage ( ms ) ;
2019-08-09 21:15:48 +00:00
if ( bitmap ! = null )
2014-12-03 23:15:26 +00:00
{
2019-08-15 00:52:21 +00:00
texture = new Texture ( General . Map . Graphics , bitmap ) ;
2013-03-18 13:52:27 +00:00
}
return texture ;
}
2019-08-08 01:19:11 +00:00
private static void CreateMesh ( ref MD3LoadResult result , List < WorldVertex > verts , List < int > indices )
2015-11-17 12:22:49 +00:00
{
//create mesh
2019-08-16 09:24:22 +00:00
Mesh mesh = new Mesh ( General . Map . Graphics , verts . ToArray ( ) , indices . ToArray ( ) ) ;
2015-11-17 12:22:49 +00:00
//store in result
result . Meshes . Add ( mesh ) ;
}
2018-07-05 09:50:03 +00:00
private static string ReadString ( BinaryReader br , int len )
2014-12-03 23:15:26 +00:00
{
2015-11-17 12:22:49 +00:00
string result = string . Empty ;
2014-02-21 14:42:12 +00:00
int i ;
2015-11-17 12:22:49 +00:00
2018-07-05 09:50:03 +00:00
for ( i = 0 ; i < len ; + + i )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
var c = br . ReadChar ( ) ;
2018-07-05 09:50:03 +00:00
if ( c = = '\0' )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
+ + i ;
break ;
}
2015-11-17 12:22:49 +00:00
result + = c ;
2013-09-11 09:47:53 +00:00
}
2015-11-17 12:22:49 +00:00
for ( ; i < len ; + + i ) br . ReadChar ( ) ;
return result ;
2013-08-08 11:04:13 +00:00
}
#endregion
}
2018-07-05 09:50:03 +00:00
}