Carnivores Wiki
Advertisement

The CAR files are used in Carnivores, Carnivores 2, and Carnivores Ice Age as 3D model files for the animals and hunter, weapons, dropship, and wind indicator, and are located in the \HUNTDAT folder (in Carnivores 2 and Ice Age, the weapon CAR files are instead located in \HUNTDAT\WEAPONS). The 3DF format is a simplified version of this format.

"CAR" may be short for "character"; note that it is not short for Carnivores, since the extension was used for a similar (though presumably slightly simpler) format in Chasm: The Rift, several years before development on Carnivores would have begun.

Code[]

There are several parts of the source code that handle CAR files.

Resources.cpp[]

This code loads CAR files.

void LoadCharacterInfo(TCharacterInfo &chinfo, char* FName)
{
   ReleaseCharacterInfo(chinfo);

   HANDLE hfile = CreateFile(FName,
      GENERIC_READ, FILE_SHARE_READ,
	  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

   if (hfile==INVALID_HANDLE_VALUE) {
      char sz[512];
      wsprintf( sz, "Error opening character file:\n%s.", FName );
      DoHalt(sz);
    }

    ReadFile(hfile, chinfo.ModelName, 32, &l, NULL);
    ReadFile(hfile, &chinfo.AniCount,  4, &l, NULL);
    ReadFile(hfile, &chinfo.SfxCount,  4, &l, NULL);

//============= read model =================//

    chinfo.mptr = (TModel*) _HeapAlloc(Heap, 0, sizeof(TModel));

    ReadFile( hfile, &chinfo.mptr->VCount,      4,         &l, NULL );
    ReadFile( hfile, &chinfo.mptr->FCount,      4,         &l, NULL );
    ReadFile( hfile, &chinfo.mptr->TextureSize, 4,         &l, NULL );
    ReadFile( hfile, chinfo.mptr->gFace,        chinfo.mptr->FCount<<6, &l, NULL );
    ReadFile( hfile, chinfo.mptr->gVertex,      chinfo.mptr->VCount<<4, &l, NULL );

    int ts = chinfo.mptr->TextureSize;
	if (HARD3D) chinfo.mptr->TextureHeight = 256;
          else  chinfo.mptr->TextureHeight = chinfo.mptr->TextureSize>>9;    
    chinfo.mptr->TextureSize = chinfo.mptr->TextureHeight*512;

    chinfo.mptr->lpTexture = (WORD*) _HeapAlloc(Heap, 0, chinfo.mptr->TextureSize);    

    ReadFile(hfile, chinfo.mptr->lpTexture, ts, &l, NULL);
    
    DATASHIFT(chinfo.mptr->lpTexture, chinfo.mptr->TextureSize);
    GenerateModelMipMaps(chinfo.mptr);
	GenerateAlphaFlags(chinfo.mptr);
	//ApplyAlphaFlags(chinfo.mptr->lpTexture, 256*256);
	//ApplyAlphaFlags(chinfo.mptr->lpTexture2, 128*128);
//============= read animations =============//
    for (int a=0; a<chinfo.AniCount; a++) {
      ReadFile(hfile, chinfo.Animation[a].aniName, 32, &l, NULL);
      ReadFile(hfile, &chinfo.Animation[a].aniKPS, 4, &l, NULL);
      ReadFile(hfile, &chinfo.Animation[a].FramesCount, 4, &l, NULL);
      chinfo.Animation[a].AniTime = (chinfo.Animation[a].FramesCount * 1000) / chinfo.Animation[a].aniKPS;
      chinfo.Animation[a].aniData = (short int*) 
          _HeapAlloc(Heap, 0, (chinfo.mptr->VCount*chinfo.Animation[a].FramesCount*6) );

      ReadFile(hfile, chinfo.Animation[a].aniData, (chinfo.mptr->VCount*chinfo.Animation[a].FramesCount*6), &l, NULL);
    }

//============= read sound fx ==============//
	BYTE tmp[32];
    for (int s=0; s<chinfo.SfxCount; s++) {
      ReadFile(hfile, tmp, 32, &l, NULL);
      ReadFile(hfile, &chinfo.SoundFX[s].length, 4, &l, NULL);
       chinfo.SoundFX[s].lpData = (short int*) _HeapAlloc(Heap, 0, chinfo.SoundFX[s].length);
      ReadFile(hfile, chinfo.SoundFX[s].lpData, chinfo.SoundFX[s].length, &l, NULL);
    }

   for (int v=0; v<chinfo.mptr->VCount; v++) {
     chinfo.mptr->gVertex[v].x*=2.f;
     chinfo.mptr->gVertex[v].y*=2.f;
     chinfo.mptr->gVertex[v].z*=-2.f;
    }

   CorrectModel(chinfo.mptr);
   
   
   ReadFile(hfile, chinfo.Anifx, 64*4, &l, NULL);
   if (l!=256)
	   for (l=0; l<64; l++) chinfo.Anifx[l] = -1;
   CloseHandle(hfile);
}

Format[]

// for each face (64 bytes)
uint32
4
v1
vertex 1
uint32
4
v2
vertex 2
uint32
4
v3
vertex 3
uint32
4
tax
v1 texture U coordinate
uint32
4
tbx
v2 texture U coordinate
uint32
4
tcx
v3 texture U coordinate
uint32
4
tay
v1 texture V coordinate
uint32
4
tby
v2 texture V coordinate
uint32
4
tcy
v3 texture V coordinate
uint16
2
flags
bit
1
sfDoubleSide
0x0001
marks face as textured on both sides
bit
1
sfDarkBack
0x0002
marks face as having a dark back side
bit
1
sfOpacity
0x0004
marks face as transparent
bit
1
sfTransparent
0x0008
marks face as non-solid (bullets pass through harmlessly)
bit
1
sfMortal
0x0010
marks face as a target zone
bit
1
sfPhong
0x0020
marks face as Phong mapped
bit
1
sfEnvMap
0x0040
marks face as Environment mapped
bit
1
sfNeedVC
0x0080
bit
7
unused
bit
1
sfDark
0x8000
marks face as having a dark front side
uint16
2
DMask
appears to be unused by the games, possibly editor/tool-specific
uint32
4
Distant
unused in-game; the game sets its own Distant flags during tree sorting of faces
uint32
4
group
appears to be unused by the games, possibly editor/tool-specific
byte
12
reserv
0x00
unused; reserved for future use
// for each vertex (16 bytes)
float
4
X coordinate
float
4
Y coordinate
float
4
Z coordinate
uint16
2
owner
bone to which vertex is attached
uint16
2
hide
whether the vertex is hidden in Designer 2; has no effect in-game
byte
texture
16-bit TGA-style encoding in BGRA5551 format, always 256 pixels wide
// for each animation (40 + (FramesCount * VCount * 6) bytes)
byte
32
aniName
name of the VTL file the animation was loaded from
uint32
4
aniKPS
keyframes per second
uint32
4
FramesCount
number of frames
byte
aniData
// for each frame (VCount * 6 bytes)
// for each vertex (6 bytes)
int16
2
X coordinate
int16
2
Y coordinate
int16
2
Z coordinate
// for each sound (36 + length bytes)
byte
32
name
uint32
4
length
byte
data (16-bit mono PCM @ 22050 Hz)
// animation/sound cross-reference table (256 bytes)
// for each entry (4 bytes)
int32
4
sound index (-1 if no sound mapped)

Notes[]

  • ModelName always consists of two parts: the first is the name of the model's texture, and the second is almost always msc: #.
    • In many models, the texture name has an additional character(s) after the first null character(s); for example, PAR2.CAR in Carnivores and Carnivores 2 has the name Par2\x00e.
    • While in most models, the msc: string starts at 24 bytes (and is thus 8 bytes long), it instead starts at 20 bytes in many Carnivores Ice Age models (making it 12 bytes long); for example, Bear.car has the value msc: 4\x00: 5. In some cases it has embedded null bytes, similar to the texture name; for example, WEAPON2.CAR in Carnivores and X_BOW.CAR in Carnivores 2 (both for the X-Bow) have the value msc: 4\x006. EXPLO.CAR has the hex value \xfa\x0f\x00\x00D\x02 instead of any "msc" string.
  • The sfPhong and sfEnvMap flags are actually assigned values of 0x30 and 0x50 in the game source, meaning models with these flags will also set sfMortal on the same faces. Fortunately, weapons are the only objects to use the sfPhong and sfEnvMap flags. Furthermore, the code will still work correctly if these flags are set but sfMortal is not. Animator uses the correct values of 0x20 and 0x40 for these flags.
  • The alpha channel in the image is unused; instead, the game engine uses the alpha flag to determine which triangles are transparent on a model.
  • Each entry in the animation/sound cross-reference table corresponds to an animation. Because of this, sounds are assigned to the animations sequentially, only one sound can be assigned to each animation, and the order of animations in the table cannot be changed.

MAXScript[]

Rexhunter99 wrote a MAXScript for importing CAR files into 3ds Max.

--(c) RexHunter99 2009


utility GeomExp "Carnivores MAXScript"
(
	group "Import"
	(
		button carImport "Import CAR" toolTip: "Import the entire scene"
	)
	group "Export"
	(
		button x3dfExport "Export 3DF" toolTip: "Export the entire scene"
		button vtlExport "Export VTL" toolTip: "Export the animated scene"
	)
	group "About"
	(
	  	label lab1 "Carnivores MAXScript"
	  	label lab2 "Version 1.2"
	  	label lab3 "Made by Rexhunter99"
	)
	
	fn ReadCAR fname =
	(
		--Define some variables
		cname = "NULL"
		msc = "msc: 0"
		num_tris = 0
		num_verts = 0
		num_anims = 0
		num_sounds = 0
		num_bones = 0
		tri_list = #()
		vert_list = #()
		tex_list = #()
		tc_x = #()
		tc_y = #()
		tex_size = 256*256*2
		
		--Read the damned file >_<
		f = fopen fname "rb"
		
		--Read the header
		cname = ReadString f
		fseek f 24 #seek_set
		msc = ReadString f
		fseek f 32 #seek_set
		num_anims = ReadLong f #signed
		num_sounds = ReadLong f #signed
		num_verts = ReadLong f #signed
		num_tris = ReadLong f #signed
		tex_size = ReadLong f #signed
		
		for t=1 to num_tris do (
			v1 = ReadLong f				--Vert 1
			v2 = ReadLong f				--Vert 2
			v3 = ReadLong f				--Vert 3
			tx1 = ReadLong f			--U1
			tx2 = ReadLong f			--U2
			tx3 = ReadLong f			--U3
			ty1 = ReadLong f			--V1
			ty2 = ReadLong f			--V2
			ty3 = ReadLong f			--V3
			flags = ReadLong f			--Flags
			fseek f 4 #seek_cur
			par = ReadLong f			--Parent
			fseek f 4 #seek_cur
			fseek f 4 #seek_cur
			fseek f 4 #seek_cur
			fseek f 4 #seek_cur
			append tri_list ([v1+1,v2+1,v3+1])
			append tc_x ((tx1/256.0) as float)
			append tc_x ((tx2/256.0) as float)
			append tc_x ((tx3/256.0) as float)
			append tc_y ((ty1/256.0) as float)
			append tc_y ((ty2/256.0) as float)
			append tc_y ((ty3/256.0) as float)
		)
		for v=1 to num_verts do (
			x = ReadFloat f				--X
			y = ReadFloat f				--Z
			z = ReadFloat f				--Y
			b = ReadLong f				--Bone
			
			if b>num_bones then
			num_bones = b
			
			append vert_list ([-x,z,y])
		)
		
		fclose f
		
		mat = multiMaterial numsubs:1 name:cname
		m = mesh name:cname position:[0, 0, 0] scale:[1, 1, 1] \
		faces:tri_list vertices:vert_list material:mat
		
		setNumTVerts m (num_tris * 3)
		for f = 1 to (num_tris * 3) do 
		(
			setTVert m (f+0) tc_x[f+0] tc_y[f+0] 0
		)
			
		-- Create our texture vertice faces
		buildTVFaces m
		for f = 1 to (num_tris-1) do
		(
			v1 = ((f - 1) * 3) + 1
			v2 = ((f - 1) * 3) + 2
			v3 = ((f - 1) * 3) + 3
			setTVFace m f [v1,v2,v3]
		)
	)
	
	fn Save3DF fname =
	(
		if fname == undefined then exit
			
		obj = 0
		obones = #()
		numbones = 0
		
		format "$objects.count = %\n" $objects.count
		
		for o=1 to $objects.count do
		(
			if $objects[o].name[1]=="#" then
			(
				append obones $objects[o]
				numbones += 1
				format "  % %\n" $objects[o].name $objects[o].parent
			)
			else
				obj = $objects[o]
		)
		
		
		if $selection[1] == undefined then
		(
			obj = $objects[1]
		)
		else
		(
			obj = $selection[1]
		)
		
		format "BoneCount = %\n" numbones
		numbones = 0
		
		fp = fopen fname "wb"
		
		WriteLong fp obj.numverts #unsigned
		WriteLong fp obj.numfaces #unsigned
		WriteLong fp numbones #unsigned
		WriteLong fp (256*512) #unsigned
		
		for i=1 to obj.numfaces do
		(
			f = getface obj i
			t = gettvface obj i
			tx = gettvert obj t.x
			ty = gettvert obj t.y
			tz = gettvert obj t.z
			WriteLong fp (f.z-1) #unsigned
			WriteLong fp (f.y-1) #unsigned
			WriteLong fp (f.x-1) #unsigned
			WriteLong fp (tz.x * 256) #unsigned
			WriteLong fp (ty.x * 256) #unsigned
			WriteLong fp (tx.x * 256) #unsigned
			WriteLong fp (tz.y * 256) #unsigned
			WriteLong fp (ty.y * 256) #unsigned
			WriteLong fp (tx.y * 256) #unsigned
			WriteLong fp 0
			WriteLong fp 0
			WriteLong fp -1
			WriteLong fp 0
			WriteLong fp 0
			WriteLong fp 0
			WriteLong fp 0
		)
		
		for i=1 to obj.numverts do
		(
			v = getvert obj i
			WriteFloat fp (-v.x)
			WriteFloat fp v.z
			WriteFloat fp v.y
			--Get the bone if the mesh is Skinned
			WriteLong fp 0
		)
		
		for i=1 to numbones do
		(
			for c=1 to 32 do
			(
				if c >= obones[i].name.length then
					WriteByte fp 0
				else
					WriteByte fp obones[i].name[c]
			)
			WriteFloat fp obones[i].x
			WriteFloat fp obones[i].y
			WriteFloat fp obones[i].z
			WriteShort fp -1
			WriteShort fp 0
		)
		
		texfp = fopen obj.material.diffuseMap.fileName "rb"
		fseek texfp 18 #seek_set
		
		for i=1 to 256*256 do
		(
			pixel = ReadShort texfp #unsigned
			WriteShort fp pixel #unsigned
		)
		
		fclose texfp
		
		fclose fp
	)
	
	fn SaveVTL fname =
	(
		if $selection[1] == undefined then
		(
			obj = $objects[1]
		)
		else
		(
			obj = $selection[1]
		)
		
		if fname == undefined then exit
			
		num_frames = ( 1 + animationRange.end - animationRange.start )
		
		fp = fopen fname "wb"
		
		WriteLong fp obj.numverts #unsigned
		WriteLong fp 20 #unsigned
		WriteLong fp num_frames #unsigned
		
		--Animation
		sliderTime = animationRange.start
		for i=animationRange.start to animationRange.end do
		(
			for j=1 to obj.numverts do
			(
				v = getvert obj j
				WriteShort fp ((-v.x*16) as integer)
				WriteShort fp ((v.z*16) as integer)
				WriteShort fp ((v.y*16) as integer)
			)
			sliderTime+=1f
		)
		sliderTime = animationRange.start
		
		fclose fp
	)
	
	on carImport pressed do
 	(
  		clearListener()
  		
		--Find a file
		fname =  getOpenFileName "Open a CAR file" \
		types:"CAR (.car)|*.car|"
		
  		ReadCAR fname
  	)
	
	on x3dfExport pressed do
 	(
  		clearListener()
  		
		--Find a file
		fname =  getSaveFileName "Save a 3DF file" \
		types:"3D File (.3DF)|*.3DF|"
		
  		Save3DF fname
  	)
	
	on vtlExport pressed do
 	(
  		clearListener()
  		
		--Find a file
		fname =  getSaveFileName "Save a VTL file" \
		types:"Vertex Transformation List (.VTL)|*.VTL|"
		
  		SaveVTL fname
  	)
)

External links[]

Advertisement