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 ;
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 ;
using SlimDX ;
using SlimDX.Direct3D9 ;
2013-04-11 09:27:16 +00:00
using CodeImp.DoomBuilder.Geometry ;
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 ;
2014-12-03 23:15:26 +00:00
public MD3LoadResult ( )
{
2013-03-18 13:52:27 +00:00
Skins = new List < string > ( ) ;
Meshes = new List < Mesh > ( ) ;
}
}
2013-08-08 11:04:13 +00:00
2015-04-14 11:33:57 +00:00
private static readonly VertexElement [ ] vertexElements = new [ ]
2014-12-03 23:15:26 +00:00
{
2015-04-14 11:33:57 +00:00
new VertexElement ( 0 , 0 , DeclarationType . Float3 , DeclarationMethod . Default , DeclarationUsage . Position , 0 ) ,
new VertexElement ( 0 , 12 , DeclarationType . Color , DeclarationMethod . Default , DeclarationUsage . Color , 0 ) ,
new VertexElement ( 0 , 16 , DeclarationType . Float2 , DeclarationMethod . Default , DeclarationUsage . TextureCoordinate , 0 ) ,
new VertexElement ( 0 , 24 , DeclarationType . Float3 , DeclarationMethod . Default , DeclarationUsage . Normal , 0 ) ,
VertexElement . VertexDeclarationEnd
} ;
2014-01-03 10:33:45 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Load
2014-12-03 23:15:26 +00:00
public static void Load ( ModelData mde , List < DataReader > containers , Device device )
{
2015-11-17 12:22:49 +00:00
if ( mde . IsVoxel ) LoadKVX ( mde , containers , device ) ;
else LoadModel ( mde , containers , device ) ;
2014-01-03 10:33:45 +00:00
}
2014-12-03 23:15:26 +00:00
private static void LoadKVX ( ModelData mde , List < DataReader > containers , Device device )
{
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
2014-12-03 23:15:26 +00:00
foreach ( DataReader dr in containers )
{
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
ReadKVX ( mde , ms , device ) ;
//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 ;
2014-12-03 23:15:26 +00:00
if ( mde . Model . Meshes = = null | | mde . Model . Meshes . Count = = 0 )
{
2014-01-03 10:33:45 +00:00
mde . Model = null ;
}
}
2014-12-03 23:15:26 +00:00
private static void LoadModel ( ModelData mde , List < DataReader > containers , Device device )
{
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
2014-12-03 23:15:26 +00:00
for ( int i = 0 ; i < mde . ModelNames . Count ; i + + )
{
2016-07-18 12:05:19 +00:00
// Use model skins?
// INFO: Skin MODELDEF property overrides both embedded surface names and ones set using SurfaceSkin MODELDEF property
Dictionary < int , string > skins = null ;
if ( string . IsNullOrEmpty ( mde . SkinNames [ i ] ) )
{
skins = ( mde . SurfaceSkinNames [ i ] . Count > 0 ? mde . SurfaceSkinNames [ i ] : new Dictionary < int , string > ( ) ) ;
}
2013-03-18 13:52:27 +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 ) ;
2015-11-17 12:22:49 +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 ;
}
2016-07-18 12:05:19 +00:00
result = ReadMD3Model ( ref bbs , skins , ms , device , mde . FrameIndices [ i ] ) ;
2015-07-15 09:09:47 +00:00
break ;
case ".md2" :
2015-11-17 12:22:49 +00:00
result = ReadMD2Model ( ref bbs , ms , device , 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" :
result = Read3DModel ( ref bbs , skins , ms , device , mde . FrameIndices [ i ] , mde . ModelNames [ i ] , containers ) ;
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?
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 ) ;
2014-12-03 23:15:26 +00:00
}
else
{
2013-03-18 13:52:27 +00:00
//add loaded data to ModeldefEntry
mde . Model . Meshes . AddRange ( result . Meshes ) ;
//prepare UnknownTexture3D... just in case :)
if ( General . Map . Data . UnknownTexture3D . Texture = = null | | General . Map . Data . UnknownTexture3D . Texture . Disposed )
General . Map . Data . UnknownTexture3D . CreateTexture ( ) ;
//load texture
List < string > errors = new List < string > ( ) ;
2015-12-17 10:07:28 +00:00
// Texture not defined in MODELDEF?
2016-07-18 12:05:19 +00:00
if ( skins ! = null )
2014-12-03 23:15:26 +00:00
{
2013-03-18 13:52:27 +00:00
//try to use model's own skins
2014-12-03 23:15:26 +00:00
for ( int m = 0 ; m < result . Meshes . Count ; m + + )
{
if ( string . IsNullOrEmpty ( result . Skins [ m ] ) )
{
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 ) ;
ext = Path . GetExtension ( path ) ;
2015-12-17 10:07:28 +00:00
if ( Array . IndexOf ( ModelData . SUPPORTED_TEXTURE_EXTENSIONS , ext ) = = - 1 )
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 ) ;
2016-02-22 08:04:06 +00:00
errors . Add ( "image format \"" + ext + "\" is not supported!" ) ;
2013-03-18 13:52:27 +00:00
continue ;
}
//relative path?
if ( path . IndexOf ( Path . DirectorySeparatorChar ) = = - 1 )
2016-07-18 12:05:19 +00:00
path = Path . Combine ( Path . GetDirectoryName ( mde . ModelNames [ i ] ) , path ) ;
2013-03-18 13:52:27 +00:00
Texture t = LoadTexture ( containers , path , device ) ;
2014-12-03 23:15:26 +00:00
if ( t = = null )
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
2016-02-22 08:04:06 +00:00
errors . Add ( "unable to load skin \"" + result . Skins [ m ] + "\"" ) ;
2013-03-18 13:52:27 +00:00
continue ;
}
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
{
2016-07-18 12:05:19 +00:00
Texture t = LoadTexture ( containers , mde . SkinNames [ i ] , device ) ;
2014-12-03 23:15:26 +00:00
if ( t = = null )
{
2013-03-18 13:52:27 +00:00
mde . Model . Textures . Add ( General . Map . Data . UnknownTexture3D . Texture ) ;
2016-07-18 12:05:19 +00:00
errors . Add ( "unable to load texture \"" + mde . SkinNames [ i ] + "\"" ) ;
2014-12-03 23:15:26 +00:00
}
else
{
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
2014-12-03 23:15:26 +00:00
if ( errors . Count > 0 )
{
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
2014-12-03 23:15:26 +00:00
if ( mde . Model . Meshes = = null | | mde . Model . Meshes . Count = = 0 )
{
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 ) ;
//calculate model radius
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 ) ) ) ;
2013-08-08 11:04:13 +00:00
}
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 )
{
case 2 :
return PadInt16 ( ( n & 0x7ff ) < < 5 ) / 127f ;
case 1 :
return PadInt16 ( ( ( ( int ) n > > 11 ) & 0x7ff ) < < 5 ) / 127f ;
case 0 :
return PadInt16 ( ( ( ( int ) n > > 22 ) & 0x3ff ) < < 6 ) / 127f ;
default :
return 0f ;
}
}
private struct UE1Poly
{
public int [ ] V ;
public float [ ] S ;
public float [ ] T ;
public int TexNum ;
}
internal static MD3LoadResult Read3DModel ( ref BoundingBoxSizes bbs , Dictionary < int , string > skins , Stream s , Device device , int frame , string filename , List < DataReader > containers )
{
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 ( ) ;
stream_d . Position + = 16 ; // bogusrot, bogusframe, bogusnorm[3]
uint d3d_fixscale = br_d . ReadUInt32 ( ) ;
stream_d . Position + = 12 ; // unused[3]
stream_d . Position + = 12 ; // padding[12]
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 ;
}
// read vertices
WorldVertex [ ] vertices = new WorldVertex [ d3d_numverts ] ;
for ( uint i = 0 ; i < d3d_numverts ; i + + )
{
WorldVertex Vert = new WorldVertex ( ) ;
stream_a . Position = start_a + ( i + frame * d3d_numverts ) * 4 ;
int v_uint = br_a . ReadInt32 ( ) ;
Vert . x = UnpackUVertex ( v_uint , 0 ) ;
Vert . y = UnpackUVertex ( v_uint , 1 ) ;
Vert . z = UnpackUVertex ( v_uint , 2 ) ;
vertices [ i ] = Vert ;
}
// read polygons
UE1Poly [ ] polys = new UE1Poly [ d3d_numpolys ] ;
int [ ] polyindexlist = new int [ d3d_numpolys * 3 ] ;
for ( uint i = 0 ; i < d3d_numpolys ; i + + )
{
//
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 ] ;
for ( int j = 0 ; j < 3 ; j + + )
polyindexlist [ i * 3 + j ] = polys [ i ] . V [ j ] = br_d . ReadInt16 ( ) ;
stream_d . Position + = 2 ;
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 ( ) ;
}
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 ;
Vector3D [ ] vert = new Vector3D [ 3 ] ;
Vector3D [ ] dir = new Vector3D [ 2 ] ;
Vector3D norm ;
//
for ( int k = 0 ; k < 3 ; k + + )
vert [ k ] = new Vector3D ( vertices [ polys [ j ] . V [ k ] ] . x , vertices [ polys [ j ] . V [ k ] ] . y , vertices [ polys [ j ] . V [ k ] ] . z ) ;
dir [ 0 ] . x = vert [ 1 ] . x - vert [ 0 ] . x ;
dir [ 0 ] . y = vert [ 1 ] . y - vert [ 0 ] . y ;
dir [ 0 ] . z = vert [ 1 ] . z - vert [ 0 ] . z ;
dir [ 1 ] . x = vert [ 2 ] . x - vert [ 0 ] . x ;
dir [ 1 ] . y = vert [ 2 ] . y - vert [ 0 ] . y ;
dir [ 1 ] . z = vert [ 2 ] . z - vert [ 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 ;
norm = norm . GetNormal ( ) ;
nsum . x + = norm . x ;
nsum . y + = norm . y ;
nsum . z + = norm . z ;
total + + ;
}
vertices [ i ] . nx = - nsum . x / total ;
vertices [ i ] . ny = - nsum . y / total ;
vertices [ i ] . nz = - nsum . z / total ;
}
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 + + )
{
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 ] ;
out_polys . Add ( out_verts . Count ) ;
out_verts . Add ( vx ) ;
}
}
2018-05-27 05:53:54 +00:00
2018-05-27 18:05:57 +00:00
CreateMesh ( device , ref result , out_verts , out_polys ) ;
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 + + )
{
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 ] ;
out_polys . Add ( out_verts . Count ) ;
out_verts . Add ( vx ) ;
}
}
CreateMesh ( device , ref result , out_verts , out_polys ) ;
result . Skins . Add ( skins . ContainsKey ( k ) ? skins [ k ] . ToLowerInvariant ( ) : string . Empty ) ;
2018-05-27 05:53:54 +00:00
}
}
return result ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = MD3
internal static MD3LoadResult ReadMD3Model ( ref BoundingBoxSizes bbs , Dictionary < int , string > skins , Stream s , Device device , 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
2015-11-17 12:22:49 +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
2015-11-17 12:22:49 +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
2014-12-03 23:15:26 +00:00
if ( ! string . IsNullOrEmpty ( error ) )
{
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 ;
2014-12-03 23:15:26 +00:00
if ( polyIndecesListsPerTexture . ContainsKey ( skin ) )
{
2013-03-18 13:52:27 +00:00
polyIndecesListsPerTexture [ skin ] . Add ( polyIndecesList ) ;
vertListsPerTexture [ skin ] . AddRange ( vertList . ToArray ( ) ) ;
vertexOffsets [ skin ] . Add ( vertList . Count ) ;
2014-12-03 23:15:26 +00:00
}
else
{
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
2016-07-18 12:05:19 +00:00
if ( ! useskins )
{
2014-12-03 23:15:26 +00:00
//create mesh
2013-03-18 13:52:27 +00:00
CreateMesh ( device , ref result , vertList , polyIndecesList ) ;
result . Skins . Add ( "" ) ;
2014-12-03 23:15:26 +00:00
}
else
{
2013-03-18 13:52:27 +00:00
//create a mesh for each surface texture
2014-12-03 23:15:26 +00:00
foreach ( KeyValuePair < string , List < List < int > > > group in polyIndecesListsPerTexture )
{
2013-03-18 13:52:27 +00:00
polyIndecesList = new List < int > ( ) ;
2013-07-15 08:32:32 +00:00
int offset = 0 ;
2013-03-18 13:52:27 +00:00
//collect indices, fix vertex offsets
2014-12-03 23:15:26 +00:00
for ( int i = 0 ; i < group . Value . Count ; i + + )
{
if ( i > 0 )
{
//TODO: Damn I need to rewrite all of this stuff from scratch...
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 ( ) ) ;
}
CreateMesh ( device , ref result , vertListsPerTexture [ group . Key ] , polyIndecesList ) ;
result . Skins . Add ( group . Key . ToLowerInvariant ( ) ) ;
}
}
2013-09-11 09:47:53 +00:00
}
return result ;
}
2015-11-17 12:22:49 +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 ;
2015-11-17 12:22:49 +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 ;
2015-11-17 12:22:49 +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
2015-11-17 12:22:49 +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
2016-01-14 11:39:52 +00:00
private static MD3LoadResult ReadMD2Model ( ref BoundingBoxSizes bbs , Stream s , Device device , 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
2015-11-17 12:22:49 +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
{
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 > ( ) ;
List < Vector2 > uvCoordsList = new List < Vector2 > ( ) ;
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
2015-11-17 12:22:49 +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
2015-11-17 12:22:49 +00:00
for ( int i = 0 ; i < num_uv ; i + + )
2013-09-11 09:47:53 +00:00
uvCoordsList . Add ( new Vector2 ( ( 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
2014-02-21 14:42:12 +00:00
Vector3 scale = new Vector3 ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ;
Vector3 translate = new Vector3 ( 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
2015-11-17 12:22:49 +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
2015-11-17 12:22:49 +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 ] ] ;
//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?
2014-12-03 23:15:26 +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 ;
2014-12-03 23:15:26 +00:00
}
else
{
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
2014-01-03 10:33:45 +00:00
Mesh mesh = new Mesh ( device , polyIndecesList . Count / 3 , vertList . Count , MeshFlags . Use32Bit | MeshFlags . IndexBufferManaged | MeshFlags . VertexBufferManaged , vertexElements ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
using ( DataStream stream = mesh . LockVertexBuffer ( LockFlags . None ) )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
stream . WriteRange ( vertList . ToArray ( ) ) ;
}
mesh . UnlockVertexBuffer ( ) ;
2012-04-17 19:13:47 +00:00
2015-11-17 12:22:49 +00:00
using ( DataStream stream = mesh . LockIndexBuffer ( LockFlags . None ) )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
stream . WriteRange ( polyIndecesList . ToArray ( ) ) ;
}
mesh . UnlockIndexBuffer ( ) ;
2012-04-17 19:13:47 +00:00
2013-09-11 09:47:53 +00:00
mesh . OptimizeInPlace ( MeshOptimizeFlags . AttributeSort ) ;
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
2014-12-03 23:15:26 +00:00
private static void ReadKVX ( ModelData mde , Stream stream , Device device )
{
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 ;
2014-12-03 23:15:26 +00:00
using ( BinaryReader reader = new BinaryReader ( stream , Encoding . ASCII ) )
{
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?..
2014-12-03 23:15:26 +00:00
for ( int i = 0 ; i < xoffset . Length ; i + + )
{
2014-01-03 10:33:45 +00:00
xoffset [ i ] = reader . ReadInt32 ( ) ;
}
2014-12-03 23:15:26 +00:00
for ( int x = 0 ; x < xsize ; x + + )
{
for ( int y = 0 ; y < ysize + 1 ; y + + )
{
2014-01-03 10:33:45 +00:00
xyoffset [ x , y ] = reader . ReadInt16 ( ) ;
}
}
//read slabs
List < int > offsets = new List < int > ( xsize * ysize ) ;
2014-12-03 23:15:26 +00:00
for ( int x = 0 ; x < xsize ; x + + )
{
for ( int y = 0 ; y < ysize ; y + + )
{
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
2014-12-03 23:15:26 +00:00
if ( ! mde . OverridePalette )
{
2014-01-03 10:33:45 +00:00
reader . BaseStream . Position = slabsEnd ;
2014-12-03 23:15:26 +00:00
for ( int i = 0 ; i < 256 ; i + + )
{
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 ) ;
}
2014-12-03 23:15:26 +00:00
}
else
{
for ( int i = 0 ; i < 256 ; i + + )
{
2014-01-03 10:33:45 +00:00
palette [ i ] = General . Map . Data . Palette [ i ] ;
}
}
2014-12-03 23:15:26 +00:00
for ( int x = 0 ; x < xsize ; x + + )
{
for ( int y = 0 ; y < ysize ; y + + )
{
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
2014-12-03 23:15:26 +00:00
while ( reader . BaseStream . Position < next )
{
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 ( ) ;
2014-12-03 23:15:26 +00:00
if ( zleng > 0 )
{
2014-01-03 10:33:45 +00:00
List < int > colorIndices = new List < int > ( zleng ) ;
2014-12-03 23:15:26 +00:00
for ( int i = 0 ; i < zleng ; i + + )
{
2014-01-03 10:33:45 +00:00
colorIndices . Add ( reader . ReadByte ( ) ) ;
}
2014-12-03 23:15:26 +00:00
if ( ( flags & 16 ) ! = 0 )
{
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 ;
2014-12-03 23:15:26 +00:00
while ( z < ztop + zleng )
{
2014-01-03 10:33:45 +00:00
int c = 0 ;
while ( z + c < ztop + zleng & & colorIndices [ cstart + c ] = = colorIndices [ cstart ] ) c + + ;
2014-12-03 23:15:26 +00:00
if ( ( flags & 1 ) ! = 0 )
{
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
}
2014-12-03 23:15:26 +00:00
if ( ( flags & 2 ) ! = 0 )
{
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
}
2014-12-03 23:15:26 +00:00
if ( ( flags & 4 ) ! = 0 )
{
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
}
2014-12-03 23:15:26 +00:00
if ( ( flags & 8 ) ! = 0 )
{
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 ;
}
2014-12-03 23:15:26 +00:00
if ( ( flags & 32 ) ! = 0 )
{
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 ) ;
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
2016-07-18 23:41:02 +00:00
// Create texture
2014-01-03 10:33:45 +00:00
MemoryStream memstream = new MemoryStream ( ( 4096 * 4 ) + 4096 ) ;
2016-02-01 22:04:00 +00:00
using ( Bitmap bmp = CreateVoxelTexture ( palette ) ) bmp . Save ( memstream , ImageFormat . Bmp ) ;
2014-01-03 10:33:45 +00:00
memstream . Seek ( 0 , SeekOrigin . Begin ) ;
Texture texture = Texture . FromStream ( device , memstream , ( int ) memstream . Length , 64 , 64 , 0 , Usage . None , Format . Unknown , Pool . Managed , Filter . Point , Filter . Box , 0 ) ;
memstream . Dispose ( ) ;
2016-07-18 23:41:02 +00:00
// Add texture
2014-01-03 10:33:45 +00:00
mde . Model . Textures . Add ( texture ) ;
2016-07-18 23:41:02 +00:00
// Create mesh
MeshFlags meshflags = MeshFlags . Managed ;
if ( indices . Count > ushort . MaxValue - 1 ) meshflags | = MeshFlags . Use32Bit ;
Mesh mesh = new Mesh ( device , facescount , verts . Count , meshflags , vertexElements ) ;
2014-01-03 10:33:45 +00:00
DataStream mstream = mesh . VertexBuffer . Lock ( 0 , 0 , LockFlags . None ) ;
mstream . WriteRange ( verts . ToArray ( ) ) ;
mesh . VertexBuffer . Unlock ( ) ;
mstream = mesh . IndexBuffer . Lock ( 0 , 0 , LockFlags . None ) ;
2016-07-18 23:41:02 +00:00
if ( indices . Count > ushort . MaxValue - 1 )
mstream . WriteRange ( indices . ToArray ( ) ) ;
else
foreach ( int index in indices ) mstream . Write ( ( ushort ) index ) ;
2014-01-03 10:33:45 +00:00
mesh . IndexBuffer . Unlock ( ) ;
mesh . OptimizeInPlace ( MeshOptimizeFlags . AttributeSort ) ;
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
2016-07-18 23:41:02 +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 ;
2014-01-03 10:33:45 +00:00
2016-07-18 23:41:02 +00:00
WorldVertex wv1 = new WorldVertex
{
x = v1 . x - pivot . x ,
y = - v1 . y + pivot . y ,
z = - v1 . z + pivot . z ,
c = - 1 ,
u = pu0 ,
v = pv0
} ;
int i1 = AddVertex ( wv1 , verts , indices , hashes ) ;
WorldVertex wv2 = new WorldVertex
{
x = v2 . x - pivot . x ,
y = - v2 . y + pivot . y ,
z = - v2 . z + pivot . z ,
c = - 1 ,
u = pu1 ,
v = pv1
} ;
AddVertex ( wv2 , verts , indices , hashes ) ;
WorldVertex wv4 = new WorldVertex
{
x = v4 . x - pivot . x ,
y = - v4 . y + pivot . y ,
z = - v4 . z + pivot . z ,
c = - 1 ,
u = pu0 ,
v = pv0
} ;
int i4 = AddVertex ( wv4 , verts , indices , hashes ) ;
WorldVertex wv3 = new WorldVertex
{
x = v3 . x - pivot . x ,
y = - v3 . y + pivot . y ,
z = - v3 . z + pivot . z ,
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
}
2014-12-03 23:15:26 +00:00
private unsafe static Bitmap CreateVoxelTexture ( PixelColor [ ] palette )
{
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 ) ;
2014-12-03 23:15:26 +00:00
if ( bmpdata ! = null )
{
2014-01-03 10:33:45 +00:00
PixelColor * pixels = ( PixelColor * ) ( bmpdata . Scan0 . ToPointer ( ) ) ;
const int numpixels = 256 ;
int i = 255 ;
2014-12-03 23:15:26 +00:00
for ( PixelColor * cp = pixels + numpixels - 1 ; cp > = pixels ; cp - - , i - - )
{
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 ) ;
2014-12-03 23:15:26 +00:00
using ( Graphics gs = Graphics . FromImage ( scaled ) )
{
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
2013-08-08 11:04:13 +00:00
#region = = = = = = = = = = = = = = = = = = Utility
2012-04-17 19:13:47 +00:00
2014-12-03 23:15:26 +00:00
private static MemoryStream LoadFile ( List < DataReader > containers , string path , bool isModel )
{
foreach ( DataReader dr in containers )
{
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 ;
}
2014-12-03 23:15:26 +00:00
private static Texture LoadTexture ( List < DataReader > containers , string path , Device device )
{
2013-03-18 13:52:27 +00:00
if ( string . IsNullOrEmpty ( path ) ) return null ;
MemoryStream ms = LoadFile ( containers , path , false ) ;
if ( ms = = null ) return null ;
Texture texture = null ;
//create texture
2014-12-03 23:15:26 +00:00
if ( Path . GetExtension ( path ) = = ".pcx" ) //pcx format requires special handling...
{
2013-03-18 13:52:27 +00:00
FileImageReader fir = new FileImageReader ( ) ;
Bitmap bitmap = fir . ReadAsBitmap ( ms ) ;
ms . Close ( ) ;
2014-12-03 23:15:26 +00:00
if ( bitmap ! = null )
{
2014-02-21 14:42:12 +00:00
BitmapData bmlock = bitmap . LockBits ( new Rectangle ( 0 , 0 , bitmap . Width , bitmap . Height ) , ImageLockMode . ReadOnly , bitmap . PixelFormat ) ;
2013-07-29 08:50:50 +00:00
texture = new Texture ( device , bitmap . Width , bitmap . Height , 1 , Usage . None , Format . A8R8G8B8 , Pool . Managed ) ;
2013-03-18 13:52:27 +00:00
2013-07-29 08:50:50 +00:00
DataRectangle textureLock = texture . LockRectangle ( 0 , LockFlags . None ) ;
textureLock . Data . WriteRange ( bmlock . Scan0 , bmlock . Height * bmlock . Stride ) ;
2013-03-18 13:52:27 +00:00
2013-07-29 08:50:50 +00:00
bitmap . UnlockBits ( bmlock ) ;
texture . UnlockRectangle ( 0 ) ;
}
2014-12-03 23:15:26 +00:00
}
else
{
2013-07-29 08:50:50 +00:00
texture = Texture . FromStream ( device , ms ) ;
2013-03-18 13:52:27 +00:00
2013-07-29 08:50:50 +00:00
ms . Close ( ) ;
2013-03-18 13:52:27 +00:00
}
return texture ;
}
2015-11-17 12:22:49 +00:00
private static void CreateMesh ( Device device , ref MD3LoadResult result , List < WorldVertex > verts , List < int > indices )
{
//create mesh
Mesh mesh = new Mesh ( device , indices . Count / 3 , verts . Count , MeshFlags . Use32Bit | MeshFlags . IndexBufferManaged | MeshFlags . VertexBufferManaged , vertexElements ) ;
using ( DataStream stream = mesh . LockVertexBuffer ( LockFlags . None ) )
{
stream . WriteRange ( verts . ToArray ( ) ) ;
}
mesh . UnlockVertexBuffer ( ) ;
using ( DataStream stream = mesh . LockIndexBuffer ( LockFlags . None ) )
{
stream . WriteRange ( indices . ToArray ( ) ) ;
}
mesh . UnlockIndexBuffer ( ) ;
mesh . OptimizeInPlace ( MeshOptimizeFlags . AttributeSort ) ;
//store in result
result . Meshes . Add ( mesh ) ;
}
2014-12-03 23:15:26 +00:00
private static string ReadString ( BinaryReader br , int len )
{
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
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 ( ) ;
2015-11-17 12:22:49 +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
}
2012-04-17 19:13:47 +00:00
}