forked from FWGS/Paranoia2
1606 lines
42 KiB
C++
1606 lines
42 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
****/
|
|
|
|
#include "csg.h"
|
|
|
|
CUtlArray<mapent_t> g_mapentities;
|
|
brush_t g_mapbrushes[MAX_MAP_BRUSHES];
|
|
int g_nummapbrushes;
|
|
int g_numparsedentities;
|
|
int g_numparsedbrushes;
|
|
|
|
static short g_groupid = 0;
|
|
static short g_world_faceinfo = -1; // shared faceinfo in case level-desginer apply it to the world but except landscapes
|
|
int g_world_luxels = 0; // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels)
|
|
static int g_brushtype = BRUSH_UNKNOWN;
|
|
|
|
const char *g_sMapType[BRUSH_COUNT] =
|
|
{
|
|
"Unknown format",
|
|
"Worldcraft 2.1",
|
|
"Valve Hammer 3.4",
|
|
"Radiant",
|
|
"QuArK"
|
|
};
|
|
|
|
/*
|
|
=================
|
|
AllocSide
|
|
|
|
allocate a new side
|
|
=================
|
|
*/
|
|
side_t *AllocSide( brush_t *brush )
|
|
{
|
|
int sideidx = brush->sides.AddToTail();
|
|
side_t *news = &brush->sides[sideidx];
|
|
|
|
memset( news, 0, sizeof( side_t ));
|
|
|
|
return news;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
AllocBrush
|
|
|
|
allocate a new local brush
|
|
=================
|
|
*/
|
|
brush_t *AllocBrush( mapent_t *entity )
|
|
{
|
|
int brushidx = entity->brushes.AddToTail();
|
|
brush_t *newb = &entity->brushes[brushidx];
|
|
|
|
memset( newb, 0, sizeof( brush_t ));
|
|
newb->sides.Purge();
|
|
|
|
return newb;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
AllocEntity
|
|
|
|
allocate a new entity
|
|
=================
|
|
*/
|
|
int AllocEntity( CUtlArray<mapent_t> *entities )
|
|
{
|
|
int index = entities->AddToTail();
|
|
mapent_t *mapent = &entities->Element( index );
|
|
|
|
memset( mapent, 0, sizeof( mapent_t ));
|
|
ClearBounds( mapent->absmin, mapent->absmax );
|
|
|
|
return index;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CopyBrush
|
|
|
|
copy specified brush into new one
|
|
=================
|
|
*/
|
|
static brush_t *CopyBrush( mapent_t *entity, const brush_t *brush )
|
|
{
|
|
int brushidx = brush - entity->brushes.Base();
|
|
brush_t *newb = AllocBrush( entity );
|
|
|
|
// maybe array is changed so we need to refresh pointer to source brush
|
|
brush = (const brush_t *)&entity->brushes[brushidx];
|
|
memcpy( newb, brush, sizeof( brush_t ));
|
|
memset( &newb->sides, 0, sizeof( newb->sides ));
|
|
newb->sides.AddMultipleToTail( brush->sides.Count() );
|
|
|
|
// duplicate brush sides into newbrush
|
|
for( int i = 0; i < brush->sides.Count(); i++ )
|
|
newb->sides[i] = brush->sides[i];
|
|
|
|
return newb;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
DeleteBrush
|
|
|
|
delete brush from local array
|
|
=================
|
|
*/
|
|
static void DeleteBrush( mapent_t *entity, const brush_t *brush )
|
|
{
|
|
int brushidx = brush - entity->brushes.Base();
|
|
|
|
if( brushidx < 0 || brushidx >= entity->brushes.Count())
|
|
COM_FatalError( "DeleteBrush: invalid brush index %d\n", brushidx );
|
|
|
|
brush_t *b = &entity->brushes[brushidx];
|
|
|
|
b->sides.Purge(); // remove brush sides
|
|
entity->brushes.Remove( brushidx );
|
|
}
|
|
|
|
/*
|
|
================
|
|
MoveBrushesToEntity
|
|
|
|
copy all the brushes into another entity
|
|
================
|
|
*/
|
|
void MoveBrushesToEntity( CUtlArray<mapent_t> *entities, mapent_t *dst, mapent_t *src )
|
|
{
|
|
int oldcount = dst->brushes.Count();
|
|
// add the brushes to the tail of local array
|
|
dst->brushes.AddMultipleToTail( src->brushes.Count() );
|
|
int entnum = dst - entities->Base();
|
|
|
|
for( int i = 0; i < src->brushes.Count(); i++ )
|
|
{
|
|
brush_t *srcb = &src->brushes[i];
|
|
brush_t *dstb = &dst->brushes[oldcount+i];
|
|
|
|
memcpy( dstb, srcb, sizeof( brush_t ));
|
|
memset( &dstb->sides, 0, sizeof( dstb->sides ));
|
|
dstb->sides.AddMultipleToTail( srcb->sides.Count() );
|
|
dstb->entitynum = entnum; // need to setup entitynum
|
|
|
|
// copy brush sides into newbrush
|
|
for( int j = 0; j < srcb->sides.Count(); j++ )
|
|
dstb->sides[j] = srcb->sides[j];
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
EntityApplyTransform
|
|
|
|
hierarchical transform brushes by entity settings
|
|
=================
|
|
*/
|
|
static void EntityApplyTransform( mapent_t *src, mapent_t *dst, bool brushentity, bool external_map, vec_t yaw_offset = 0.0 )
|
|
{
|
|
vec3_t origin, angles, dscale, iscale;
|
|
matrix3x4 ptransform, itransform;
|
|
vec_t temp;
|
|
|
|
GetVectorForKey( (entity_t *)src, "origin", origin );
|
|
VectorSet( dscale, 1.0, 1.0, 1.0 );
|
|
VectorClear( angles );
|
|
|
|
if( external_map )
|
|
{
|
|
// TyrTtils external map
|
|
if( CheckKey( (entity_t *)src, "_external_map_angles" ))
|
|
GetVectorForKey( (entity_t *)src, "_external_map_angles", angles );
|
|
|
|
if( CheckKey( (entity_t *)src, "_external_map_angle" ))
|
|
angles[1] = FloatForKey( (entity_t *)src, "_external_map_angle" );
|
|
|
|
if( CheckKey( (entity_t *)src, "_external_map_scale" ))
|
|
{
|
|
switch( GetVectorForKey( (entity_t *)src, "_external_map_scale", iscale ))
|
|
{
|
|
case 1:
|
|
VectorSet( dscale, iscale[0], iscale[0], iscale[0] );
|
|
break;
|
|
case 3:
|
|
VectorCopy( iscale, dscale );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
temp = FloatForKey( (entity_t *)src, "modelscale" );
|
|
if( temp != 0.0f ) VectorSet( dscale, temp, temp, temp );
|
|
|
|
if( CheckKey( (entity_t *)src, "modelscale_vec" ))
|
|
GetVectorForKey( (entity_t *)src, "modelscale_vec", dscale );
|
|
|
|
if( CheckKey( (entity_t *)src, "angles" ))
|
|
GetVectorForKey( (entity_t *)src, "angles", angles );
|
|
|
|
if( CheckKey( (entity_t *)src, "angle" ))
|
|
angles[1] = FloatForKey( (entity_t *)src, "angle" );
|
|
}
|
|
|
|
angles[1] += yaw_offset;
|
|
COM_NormalizeAngles( angles );
|
|
VectorSet( iscale, 1.0 / dscale[0], 1.0 / dscale[1], 1.0 / dscale[2] );
|
|
Matrix3x4_CreateFromEntityScale3f( ptransform, angles, origin, dscale );
|
|
Matrix3x4_CreateFromEntityScale3f( itransform, angles, origin, iscale );
|
|
|
|
if( brushentity )
|
|
{
|
|
for( int i = 0; i < dst->brushes.Count(); i++ )
|
|
{
|
|
brush_t *b = &dst->brushes[i];
|
|
|
|
for( int j = 0; j < b->sides.Count(); j++ )
|
|
{
|
|
side_t *s = &b->sides[j];
|
|
|
|
// transform plane points
|
|
Matrix3x4_VectorTransform( ptransform, s->planepts[0], s->planepts[0] );
|
|
Matrix3x4_VectorTransform( ptransform, s->planepts[1], s->planepts[1] );
|
|
Matrix3x4_VectorTransform( ptransform, s->planepts[2], s->planepts[2] );
|
|
|
|
// transform texture vectors
|
|
Matrix3x4_VectorRotate( itransform, s->vecs[0], s->vecs[0] );
|
|
Matrix3x4_VectorRotate( itransform, s->vecs[1], s->vecs[1] );
|
|
s->vecs[0][3] -= DotProduct( origin, s->vecs[0] );
|
|
s->vecs[1][3] -= DotProduct( origin, s->vecs[1] );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vec3_t dst_origin, dst_angles, dst_scale;
|
|
matrix3x4 dst_transform, out_matrix;
|
|
vec3_t default_scale, dummy;
|
|
|
|
VectorSet( default_scale, 1.0, 1.0, 1.0 );
|
|
|
|
// a point entity
|
|
GetVectorForKey( (entity_t *)dst, "origin", dst_origin );
|
|
GetVectorForKey( (entity_t *)dst, "angles", dst_angles );
|
|
VectorSet( dst_scale, 1.0, 1.0, 1.0 );
|
|
|
|
if( CheckKey( (entity_t *)dst, "xform" ))
|
|
GetVectorForKey( (entity_t *)dst, "xform", dst_scale );
|
|
else if( CheckKey( (entity_t *)dst, "scale" ))
|
|
{
|
|
temp = FloatForKey( (entity_t *)src, "scale" );
|
|
if( temp != 0.0f ) VectorSet( dst_scale, temp, temp, temp );
|
|
}
|
|
|
|
// re-create source matrix with default scale
|
|
Matrix3x4_CreateFromEntityScale3f( ptransform, angles, origin, default_scale );
|
|
Matrix3x4_CreateFromEntityScale3f( dst_transform, dst_angles, dst_origin, default_scale );
|
|
Matrix3x4_ConcatTransforms( out_matrix, ptransform, dst_transform );
|
|
Matrix3x4_MatrixToEntityScale3f( out_matrix, dst_origin, dst_angles, dummy );
|
|
|
|
// NOTE: point entities ignore scale for transformation
|
|
dst_scale[0] *= dscale[0];
|
|
dst_scale[1] *= dscale[1];
|
|
dst_scale[2] *= dscale[2];
|
|
|
|
// set transformed values back
|
|
SetVectorForKey( (entity_t *)dst, "origin", dst_origin, true );
|
|
SetVectorForKey( (entity_t *)dst, "angles", dst_angles, true );
|
|
SetVectorForKey( (entity_t *)dst, "xform", dst_scale, true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SetupSideContents
|
|
|
|
side contents must be set as soon as possible
|
|
=================
|
|
*/
|
|
static void SetupSideContents( brush_t *brush, side_t *side )
|
|
{
|
|
if( !Q_strnicmp( side->name, "sky", 3 ))
|
|
{
|
|
SetBits( side->flags, FSIDE_SKY );
|
|
side->contents = CONTENTS_SKY;
|
|
return;
|
|
}
|
|
|
|
if( side->name[0] == '!' || side->name[0] == '*' )
|
|
{
|
|
if( !Q_strnicmp( side->name + 1, "lava", 4 ))
|
|
side->contents = CONTENTS_LAVA;
|
|
else if( !Q_strnicmp( side->name + 1, "slime", 5 ))
|
|
side->contents = CONTENTS_SLIME;
|
|
else side->contents = CONTENTS_WATER; // otherwise it's water
|
|
|
|
return;
|
|
}
|
|
|
|
if( !Q_strnicmp( side->name, "water", 5 ))
|
|
{
|
|
side->contents = CONTENTS_WATER;
|
|
return;
|
|
}
|
|
|
|
// special case for origin-brush
|
|
if( !Q_strnicmp( side->name, "origin", 6 ))
|
|
{
|
|
SetBits( brush->flags, FBRUSH_REMOVE );
|
|
side->contents = CONTENTS_ORIGIN;
|
|
return;
|
|
}
|
|
|
|
// this is needs only at build brush stage
|
|
if( !Q_strnicmp( side->name, "skip", 4 ) || !Q_strnicmp( side->name, "hintskip", 8 ))
|
|
{
|
|
SetBits( side->flags, FSIDE_SKIP );
|
|
side->contents = CONTENTS_EMPTY;
|
|
return;
|
|
}
|
|
|
|
if( side->name[0] == '@' || !Q_strnicmp( side->name, "translucent", 11 ))
|
|
{
|
|
side->contents = CONTENTS_TRANSLUCENT;
|
|
return;
|
|
}
|
|
|
|
if( !Q_strnicmp( side->name, "fog", 3 ))
|
|
{
|
|
side->contents = CONTENTS_FOG;
|
|
return;
|
|
}
|
|
|
|
if( !Q_strnicmp( side->name, "hint", 4 ))
|
|
{
|
|
SetBits( side->flags, FSIDE_HINT );
|
|
side->contents = CONTENTS_EMPTY;
|
|
return;
|
|
}
|
|
|
|
if( !Q_strnicmp( side->name, "solidhint", 9 ))
|
|
SetBits( side->flags, FSIDE_SOLIDHINT|FSIDE_NODRAW );
|
|
|
|
if( !Q_strnicmp( side->name, "null", 4 )) // structural clip
|
|
SetBits( side->flags, FSIDE_NODRAW );
|
|
|
|
if( !Q_strnicmp( side->name, "clip", 4 ))
|
|
SetBits( brush->flags, FBRUSH_CLIPONLY );
|
|
|
|
// all other it's a solid contents
|
|
side->contents = CONTENTS_SOLID;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SetupSideParams
|
|
|
|
load shader, apply side flags
|
|
=================
|
|
*/
|
|
static void SetupSideParams( mapent_t *mapent, brush_t *brush, side_t *side )
|
|
{
|
|
const char *classname = ValueForKey( (entity_t *)mapent, "classname" );
|
|
|
|
// check for Quake1 issues
|
|
if( side->name[0] == '*' )
|
|
side->name[0] = '!';
|
|
|
|
SetupSideContents( brush, side );
|
|
|
|
// try to find shader for this side
|
|
side->shader = ShaderInfoForShader( side->name );
|
|
|
|
// no user shader specified, ignore it
|
|
if( !FBitSet( side->shader->flags, FSHADER_DEFAULTED ))
|
|
{
|
|
// update contents from shader
|
|
if( side->shader->contents )
|
|
side->contents = side->shader->contents;
|
|
|
|
if( side->shader->contents == CONTENTS_SKY )
|
|
SetBits( side->flags, FSIDE_SKY );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_NOCLIP ))
|
|
SetBits( brush->flags, FBRUSH_NOCLIP );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_TRIGGER ))
|
|
SetBits( brush->flags, FBRUSH_CLIPONLY );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_NODRAW ))
|
|
SetBits( side->flags, FSIDE_NODRAW );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_DETAIL ))
|
|
brush->detaillevel = 1;
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_NOLIGHTMAP ))
|
|
SetBits( side->flags, FSIDE_NOLIGHTMAP );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_SKIP ))
|
|
SetBits( side->flags, FSIDE_SKIP );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_CLIP ))
|
|
SetBits( brush->flags, FBRUSH_CLIPONLY );
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_HINT ))
|
|
{
|
|
Q_strncpy( side->name, "HINT", sizeof( side->name ));
|
|
SetBits( side->flags, FSIDE_HINT );
|
|
}
|
|
|
|
if( FBitSet( side->shader->flags, FSHADER_REMOVE ))
|
|
SetBits( brush->flags, FBRUSH_REMOVE );
|
|
}
|
|
|
|
// don't compute lightmaps for sky and triggers
|
|
if( side->contents == CONTENTS_SKY )
|
|
SetBits( side->flags, FSIDE_NOLIGHTMAP );
|
|
|
|
if( !Q_strnicmp( side->name, "trigger", 7 ) || !Q_strnicmp( side->name, "aaatrigger", 10 ))
|
|
{
|
|
SetBits( side->flags, FSIDE_NOLIGHTMAP );
|
|
SetBits( brush->flags, FBRUSH_NOCSG );
|
|
}
|
|
|
|
// for each face of each brush of this entity
|
|
if( BoolForKey( (entity_t *)mapent, "zhlt_invisible" ) && side->contents != CONTENTS_ORIGIN )
|
|
SetBits( side->flags, FSIDE_NODRAW );
|
|
|
|
const int shadow = IntForKey( (entity_t *)mapent, "_shadow" );
|
|
|
|
if( IntForKey( (entity_t *)mapent, "_dirt" ) == -1 )
|
|
SetBits( side->flags, FSIDE_NODIRT );
|
|
|
|
if( shadow == -1 )
|
|
SetBits( side->flags, FSIDE_NOSHADOW );
|
|
|
|
if( !Q_stricmp( classname, "func_detail_illusionary" ))
|
|
{
|
|
// mark these entities as TEX_NOSHADOW unless the mapper set "_shadow" "1"
|
|
if( shadow != 1 ) SetBits( side->flags, FSIDE_NOSHADOW );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SetupTextureVectors
|
|
|
|
parse and setup tex->vecs
|
|
=================
|
|
*/
|
|
static void SetupTextureVectors( mapent_t *mapent, brush_t *brush, side_t *side, short &brush_type )
|
|
{
|
|
int side_flags = 0;
|
|
bool read_flags;
|
|
texvecs_t tex_vects;
|
|
|
|
if( brush_type == BRUSH_RADIANT )
|
|
Parse2DMatrix( 2, 3, (vec_t *)tex_vects.matrix );
|
|
|
|
// read the texturedef
|
|
GetToken( false );
|
|
|
|
Q_strncpy( side->name, token, sizeof( side->name ));
|
|
SetupSideParams( mapent, brush, side );
|
|
|
|
// continue determine brush type
|
|
if( brush_type != BRUSH_RADIANT )
|
|
GetToken( false );
|
|
|
|
if( brush_type == BRUSH_WORLDCRAFT_22 || !Q_strcmp( token, "[" )) // Worldcraft 2.2+
|
|
{
|
|
brush_type = BRUSH_WORLDCRAFT_22;
|
|
|
|
// texture U axis
|
|
if( Q_strcmp( token, "[" ))
|
|
COM_FatalError( "missing '[' in texturedef (U) at line %i\n", scriptline );
|
|
|
|
GetToken( false );
|
|
tex_vects.UAxis[0] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.UAxis[1] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.UAxis[2] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.shift[0] = atof( token );
|
|
GetToken( false );
|
|
|
|
if( Q_strcmp( token, "]" ))
|
|
COM_FatalError( "missing ']' in texturedef (U) at line %i\n", scriptline );
|
|
|
|
// texture V axis
|
|
GetToken( false );
|
|
|
|
if( Q_strcmp( token, "[" ))
|
|
COM_FatalError( "missing '[' in texturedef (V) at line %i\n", scriptline );
|
|
|
|
GetToken( false );
|
|
tex_vects.VAxis[0] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.VAxis[1] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.VAxis[2] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.shift[1] = atof( token );
|
|
GetToken( false );
|
|
|
|
if( Q_strcmp( token, "]" ))
|
|
COM_FatalError( "missing ']' in texturedef (V) at line %i\n", scriptline );
|
|
|
|
// texture rotation is implicit in U/V axes.
|
|
GetToken( false );
|
|
tex_vects.rotate = 0.0;
|
|
|
|
// texure scale
|
|
GetToken( false );
|
|
tex_vects.scale[0] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.scale[1] = atof( token );
|
|
}
|
|
else if( brush_type == BRUSH_WORLDCRAFT_21 || brush_type == BRUSH_QUARK )
|
|
{
|
|
// worldcraft 2.1-, old Radiant, QuArK
|
|
tex_vects.shift[0] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.shift[1] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.rotate = atof( token );
|
|
GetToken( false );
|
|
tex_vects.scale[0] = atof( token );
|
|
GetToken( false );
|
|
tex_vects.scale[1] = atof( token );
|
|
}
|
|
|
|
// can't read it here because we're unfinished detecting map type
|
|
read_flags = TryToken( ) ? true : false;
|
|
if( read_flags ) side_flags = atoi( token );
|
|
|
|
if( g_TXcommand == '1' || g_TXcommand == '2' )
|
|
{
|
|
vec_t a, b, c, d, determinant;
|
|
vec3_t vecs[2];
|
|
|
|
brush_type = BRUSH_QUARK;
|
|
|
|
// we are QuArK mode and need to translate some numbers to align textures it's way
|
|
// from QuArK, the texture vectors are given directly from the three points
|
|
switch( g_TXcommand - '0' )
|
|
{
|
|
case 1:
|
|
VectorSubtract( side->planepts[2], side->planepts[0], vecs[0] );
|
|
VectorSubtract( side->planepts[1], side->planepts[0], vecs[1] );
|
|
break;
|
|
case 2:
|
|
VectorSubtract( side->planepts[1], side->planepts[0], vecs[0] );
|
|
VectorSubtract( side->planepts[2], side->planepts[0], vecs[1] );
|
|
break;
|
|
default:
|
|
COM_FatalError( "QuArK: bad TX%d type\n", g_TXcommand - '0' );
|
|
}
|
|
|
|
// default QuArK scale correction
|
|
VectorScale( vecs[0], 1.0 / 128.0, vecs[0] );
|
|
VectorScale( vecs[1], 1.0 / 128.0, vecs[1] );
|
|
|
|
a = DotProduct( vecs[0], vecs[0] );
|
|
b = DotProduct( vecs[0], vecs[1] );
|
|
c = DotProduct( vecs[1], vecs[0] ); // b == c
|
|
d = DotProduct( vecs[1], vecs[1] );
|
|
determinant = a * d - b * c;
|
|
|
|
if( fabs( determinant ) > 1e-6 )
|
|
{
|
|
for( int i = 0; i < 3; i++ )
|
|
{
|
|
side->vecs[0][i] = (d * vecs[0][i] - b * vecs[1][i]) / determinant;
|
|
side->vecs[1][i] =-(a * vecs[1][i] - c * vecs[0][i]) / determinant;
|
|
}
|
|
|
|
side->vecs[0][3] = -DotProduct( side->vecs[0], side->planepts[0] );
|
|
side->vecs[1][3] = -DotProduct( side->vecs[1], side->planepts[0] );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_WARN, "Degenerate QuArK-style brush texture: Entity %i, Brush %i\n",
|
|
brush->originalentitynum, brush->originalbrushnum );
|
|
}
|
|
}
|
|
else if( brush_type == BRUSH_WORLDCRAFT_21 || brush_type == BRUSH_WORLDCRAFT_22 )
|
|
{
|
|
vec3_t axis[2];
|
|
int i, j;
|
|
|
|
if( brush_type == BRUSH_WORLDCRAFT_21 )
|
|
TextureAxisFromSide( side, axis[0], axis[1], false );
|
|
|
|
if( !tex_vects.scale[0] ) tex_vects.scale[0] = 1.0;
|
|
if( !tex_vects.scale[1] ) tex_vects.scale[1] = 1.0;
|
|
|
|
if( brush_type == BRUSH_WORLDCRAFT_21 )
|
|
{
|
|
vec_t sinv, cosv;
|
|
vec_t ns, nt;
|
|
int sv, tv;
|
|
|
|
// rotate axis
|
|
if( tex_vects.rotate == 0.0 )
|
|
{
|
|
sinv = 0.0;
|
|
cosv = 1.0;
|
|
}
|
|
else if( tex_vects.rotate == 90.0 )
|
|
{
|
|
sinv = 1.0;
|
|
cosv = 0.0;
|
|
}
|
|
else if( tex_vects.rotate == 180.0 )
|
|
{
|
|
sinv = 0.0;
|
|
cosv = -1.0;
|
|
}
|
|
else if( tex_vects.rotate == 270.0 )
|
|
{
|
|
sinv = -1.0;
|
|
cosv = 0.0;
|
|
}
|
|
else
|
|
{
|
|
vec_t ang = DEG2RAD( tex_vects.rotate );
|
|
|
|
sinv = sin( ang );
|
|
cosv = cos( ang );
|
|
}
|
|
|
|
if( axis[0][0] )
|
|
sv = 0;
|
|
else if( axis[0][1] )
|
|
sv = 1;
|
|
else sv = 2;
|
|
|
|
if( axis[1][0] )
|
|
tv = 0;
|
|
else if( axis[1][1] )
|
|
tv = 1;
|
|
else tv = 2;
|
|
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
ns = cosv * axis[i][sv] - sinv * axis[i][tv];
|
|
nt = sinv * axis[i][sv] + cosv * axis[i][tv];
|
|
axis[i][sv] = ns;
|
|
axis[i][tv] = nt;
|
|
}
|
|
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
side->vecs[i][j] = axis[i][j] / tex_vects.scale[i];
|
|
}
|
|
}
|
|
}
|
|
else if( brush_type == BRUSH_WORLDCRAFT_22 )
|
|
{
|
|
for( j = 0; j < 3; j++ )
|
|
side->vecs[0][j] = tex_vects.UAxis[j] / tex_vects.scale[0];
|
|
for( j = 0; j < 3; j++ )
|
|
side->vecs[1][j] = tex_vects.VAxis[j] / tex_vects.scale[1];
|
|
}
|
|
|
|
side->vecs[0][3] = tex_vects.shift[0];
|
|
side->vecs[1][3] = tex_vects.shift[1];
|
|
}
|
|
else if( brush_type == BRUSH_RADIANT )
|
|
{
|
|
int width, height;
|
|
vec3_t axis[2];
|
|
|
|
TEX_GetSize( side->shader->imagePath, &width, &height );
|
|
TextureAxisFromSide( side, axis[0], axis[1], true );
|
|
|
|
side->vecs[0][0] = width * ((axis[0][0] * tex_vects.matrix[0][0]) + (axis[1][0] * tex_vects.matrix[0][1]));
|
|
side->vecs[0][1] = width * ((axis[0][1] * tex_vects.matrix[0][0]) + (axis[1][1] * tex_vects.matrix[0][1]));
|
|
side->vecs[0][2] = width * ((axis[0][2] * tex_vects.matrix[0][0]) + (axis[1][2] * tex_vects.matrix[0][1]));
|
|
side->vecs[0][3] = width * tex_vects.matrix[0][2];
|
|
|
|
side->vecs[1][0] = height * ((axis[0][0] * tex_vects.matrix[1][0]) + (axis[1][0] * tex_vects.matrix[1][1]));
|
|
side->vecs[1][1] = height * ((axis[0][1] * tex_vects.matrix[1][0]) + (axis[1][1] * tex_vects.matrix[1][1]));
|
|
side->vecs[1][2] = height * ((axis[0][2] * tex_vects.matrix[1][0]) + (axis[1][2] * tex_vects.matrix[1][1]));
|
|
side->vecs[1][3] = height * tex_vects.matrix[1][2];
|
|
}
|
|
|
|
// Quake3 detail brushes or Volatile3D detail brushes
|
|
if( FBitSet( side_flags, 0x8000000 ) || FBitSet( side_flags, 0x400000 ))
|
|
{
|
|
// enable detaillevel feature on solid brushes
|
|
if( side->contents == CONTENTS_SOLID )
|
|
brush->detaillevel = 1;
|
|
// SetBits( brush->flags, FBRUSH_NOCSG );
|
|
}
|
|
|
|
// Doom2Gold extrainfo
|
|
if( g_DXspecial != 0 )
|
|
{
|
|
dfaceinfo_t *fi = NULL;
|
|
short doomlight;
|
|
|
|
if( FBitSet( g_DXspecial, (1<<16) ))
|
|
{
|
|
SetBits( side->flags, FSIDE_SCROLL );
|
|
ClearBits( g_DXspecial, (1<<16));
|
|
}
|
|
|
|
doomlight = (short)g_DXspecial;
|
|
|
|
// check for already exist faceinfo
|
|
if( side->faceinfo != -1 )
|
|
{
|
|
fi = &g_dfaceinfo[side->faceinfo];
|
|
side->faceinfo = FaceinfoForTexinfo( fi->landname, fi->texture_step, fi->max_extent, doomlight );
|
|
}
|
|
else
|
|
{
|
|
// build from scratch
|
|
side->faceinfo = FaceinfoForTexinfo( "", TEXTURE_STEP, MAX_SURFACE_EXTENT, doomlight );
|
|
}
|
|
}
|
|
|
|
// first token it's already waiting
|
|
if( read_flags )
|
|
{
|
|
TryToken(); // unused
|
|
TryToken(); // unused
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
ParseBrush
|
|
=================
|
|
*/
|
|
static void ParseBrush( mapent_t *mapent, short entindex, short faceinfo, short &brush_type )
|
|
{
|
|
brush_t *brush;
|
|
side_t *side;
|
|
int i;
|
|
|
|
if( brush_type == BRUSH_RADIANT )
|
|
CheckToken( "{" );
|
|
|
|
brush = AllocBrush( mapent );
|
|
brush->originalentitynum = g_numparsedentities;
|
|
brush->originalbrushnum = g_numparsedbrushes;
|
|
brush->entitynum = entindex;
|
|
|
|
// read ZHLT settings for this brush
|
|
brush->detaillevel = Q_max( IntForKey( (entity_t *)mapent, "zhlt_detaillevel" ), 0 );
|
|
|
|
if( BoolForKey( (entity_t *)mapent, "zhlt_noclip" ))
|
|
SetBits( brush->flags, FBRUSH_NOCLIP );
|
|
if( BoolForKey( (entity_t *)mapent, "zhlt_nocsg" ))
|
|
SetBits( brush->flags, FBRUSH_NOCSG );
|
|
|
|
while( 1 )
|
|
{
|
|
g_TXcommand = 0;
|
|
g_DXspecial = 0;
|
|
|
|
if( !GetToken( true ))
|
|
break;
|
|
|
|
if( !Q_strcmp( token, "}" ))
|
|
break;
|
|
|
|
if( brush_type == BRUSH_RADIANT )
|
|
{
|
|
while( 1 )
|
|
{
|
|
if( Q_strcmp( token, "(" ))
|
|
GetToken( false );
|
|
else break;
|
|
GetToken( true );
|
|
}
|
|
}
|
|
|
|
side = AllocSide( brush );
|
|
UnGetToken();
|
|
|
|
// read the three point plane definition
|
|
Parse1DMatrix( 3, side->planepts[0] );
|
|
Parse1DMatrix( 3, side->planepts[1] );
|
|
Parse1DMatrix( 3, side->planepts[2] );
|
|
|
|
// store faceinfo number for group of brushes. Otherwise write -1
|
|
side->faceinfo = faceinfo;
|
|
|
|
SetupTextureVectors( mapent, brush, side, brush_type );
|
|
}
|
|
|
|
if( brush_type == BRUSH_RADIANT )
|
|
{
|
|
UnGetToken();
|
|
CheckToken( "}" );
|
|
CheckToken( "}" );
|
|
}
|
|
|
|
brush->contents = BrushContents( mapent, brush );
|
|
|
|
// check for faces that should be a nullify
|
|
for( i = 0; i < brush->sides.Count(); i++ )
|
|
{
|
|
side = &brush->sides[i];
|
|
|
|
// remove aaatrigger from the world and brush entities
|
|
if(( g_nullifytrigger || brush->entitynum == 0 ) && ( !Q_strnicmp( side->name, "AAATRIGGER", 10 ) || !Q_strnicmp( side->name, "trigger", 7 )))
|
|
SetBits( side->flags, FSIDE_NODRAW );
|
|
}
|
|
|
|
// origin brushes are removed, but they set
|
|
// the rotation origin for the rest of the brushes
|
|
// in the entity
|
|
if( brush->contents == CONTENTS_ORIGIN )
|
|
{
|
|
char string[32];
|
|
vec3_t origin;
|
|
|
|
CreateBrushFaces( brush ); // to get sizes
|
|
DeleteBrushFaces( brush );
|
|
|
|
if( brush->entitynum != 0 )
|
|
{
|
|
// ignore for WORLD (code elsewhere enforces no ORIGIN in world message)
|
|
VectorAverage( brush->hull[0].mins, brush->hull[0].maxs, origin );
|
|
Q_snprintf( string, sizeof( string ), "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2] );
|
|
SetKeyValue( (entity_t *)mapent, "origin", string );
|
|
}
|
|
}
|
|
|
|
// brush will no longer used and should be removed
|
|
if( FBitSet( brush->flags, FBRUSH_REMOVE ))
|
|
{
|
|
DeleteBrush( mapent, brush );
|
|
return;
|
|
}
|
|
|
|
// apply clip to the sky (NOTE: after call CopyBrush brush ptr is possibly invalidate)
|
|
if( brush->contents == CONTENTS_SKY && !FBitSet( brush->flags, FBRUSH_NOCLIP ))
|
|
{
|
|
brush_t *newb = CopyBrush( mapent, brush );
|
|
SetBits( newb->flags, FBRUSH_CLIPONLY );
|
|
newb->contents = CONTENTS_SOLID;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseMapEntity
|
|
================
|
|
*/
|
|
short GetFaceInfoForEntity( mapent_t *mapent )
|
|
{
|
|
if( g_onlyents ) return -1; // don't modify g_dfaceinfo!
|
|
|
|
// NOTE: we guranteed what this function called only for non-point entities
|
|
const char *landname = ValueForKey( (entity_t *)mapent, "zhlt_landscape" );
|
|
int texture_step = Q_max( 0, IntForKey( (entity_t *)mapent, "zhlt_texturestep" ));
|
|
int max_extent = Q_max( 0, IntForKey( (entity_t *)mapent, "zhlt_maxextent" ));
|
|
int global_texture_step = TEXTURE_STEP, global_max_extent = MAX_SURFACE_EXTENT;
|
|
int lmscale = TEXTURE_STEP * Q_max( 0, FloatForKey( (entity_t *)mapent, "_lmscale" ));
|
|
short faceinfo = -1;
|
|
|
|
// TyrUtils: handle _lmscale feature
|
|
if( lmscale && lmscale != TEXTURE_STEP )
|
|
texture_step = lmscale;
|
|
|
|
// update global settings
|
|
if( g_world_faceinfo != -1 )
|
|
{
|
|
global_texture_step = g_dfaceinfo[g_world_faceinfo].texture_step;
|
|
global_max_extent = g_dfaceinfo[g_world_faceinfo].max_extent;
|
|
}
|
|
|
|
// setup the global parms for world entity
|
|
if( g_mapentities.Count() == 1 )
|
|
{
|
|
// if user not specified landscape but desire to change lightmap resolution\subdiv size globally
|
|
if( !ValueForKey( (entity_t *)mapent, "zhlt_landscape", true ))
|
|
{
|
|
// get defaults
|
|
if( !max_extent ) max_extent = MAX_SURFACE_EXTENT;
|
|
if( !texture_step ) texture_step = TEXTURE_STEP;
|
|
|
|
// check for default values
|
|
if( max_extent == MAX_SURFACE_EXTENT && texture_step == TEXTURE_STEP )
|
|
return -1; // nothing changed
|
|
|
|
// store a global settings for lightmap resoltion\subdiv size
|
|
g_world_faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, 0 );
|
|
|
|
return g_world_faceinfo;
|
|
}
|
|
else // user specified landscape for world-entity, so all other entities will be ingnore them
|
|
{
|
|
// get defaults
|
|
if( !max_extent ) max_extent = MAX_SURFACE_EXTENT;
|
|
if( !texture_step ) texture_step = TEXTURE_STEP;
|
|
g_groupid++; // increase group number because landscape was specified
|
|
|
|
// now we specified a global landscape for all the world brushes but except all other entities (include func_group etc)
|
|
faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, g_groupid );
|
|
|
|
return faceinfo;
|
|
}
|
|
}
|
|
else // all other non-point ents
|
|
{
|
|
// all the fields are completely missed, so we can use world setttings
|
|
if( !CheckKey( (entity_t *)mapent, "zhlt_landscape" ) && !CheckKey( (entity_t *)mapent, "zhlt_texturestep" ) && !CheckKey( (entity_t *)mapent, "zhlt_maxextent" ))
|
|
{
|
|
return g_world_faceinfo; // nothing changed
|
|
}
|
|
|
|
// if user not specified landscape but desire to change lightmap resolution\subdiv size
|
|
if( !ValueForKey( (entity_t *)mapent, "zhlt_landscape", true ))
|
|
{
|
|
// get defaults
|
|
if( !max_extent ) max_extent = global_max_extent;
|
|
if( !texture_step ) texture_step = global_texture_step;
|
|
|
|
// check for default values
|
|
if( max_extent == global_max_extent && texture_step == global_texture_step )
|
|
return g_world_faceinfo; // nothing changed
|
|
|
|
// store a global settings for lightmap resoltion\subdiv size
|
|
faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, 0 );
|
|
|
|
return faceinfo;
|
|
}
|
|
else // user specified landscape for world-entity, so all other entities will be ingnore them
|
|
{
|
|
// get defaults
|
|
if( !max_extent ) max_extent = global_max_extent;
|
|
if( !texture_step ) texture_step = global_texture_step;
|
|
g_groupid++; // increase group number because landscape was specified
|
|
|
|
// now we specified a global landscape for all the world brushes but except all other entities (include func_group etc)
|
|
faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, g_groupid );
|
|
|
|
return faceinfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseMapEntity
|
|
================
|
|
*/
|
|
bool ParseMapEntity( CUtlArray<mapent_t> *entities, bool external = false )
|
|
{
|
|
short brush_type;
|
|
short faceinfo = -1;
|
|
char str[128];
|
|
mapent_t *mapent;
|
|
int index;
|
|
epair_t *e;
|
|
|
|
brush_type = BRUSH_UNKNOWN;
|
|
g_numparsedbrushes = 0;
|
|
|
|
if( !GetToken( true ))
|
|
return false;
|
|
|
|
if( Q_strcmp( token, "{" ))
|
|
COM_FatalError( "ParseEntity: { not found at line %i\n", scriptline );
|
|
|
|
index = AllocEntity( entities );
|
|
mapent = &entities->Element( index );
|
|
|
|
while( 1 )
|
|
{
|
|
if( !GetToken( true ))
|
|
COM_FatalError( "ParseEntity: EOF without closing brace at line %i\n", scriptline );
|
|
|
|
if( !Q_strcmp( token, "}" ))
|
|
{
|
|
// in case worldspawn doesn't contain any brushes
|
|
if( entities->Count() == 1 && !mapent->brushes.Count( ))
|
|
TEX_LoadTextures( entities, external );
|
|
break;
|
|
}
|
|
|
|
if( !Q_strcmp( token, "{" ))
|
|
{
|
|
// parse a brush or patch
|
|
if( !GetToken( true ))
|
|
break;
|
|
|
|
// "wad" field is now valid and can be parsed
|
|
if( entities->Count() == 1 && !mapent->brushes.Count( ))
|
|
TEX_LoadTextures( entities, external );
|
|
|
|
if( !mapent->brushes.Count( ))
|
|
faceinfo = GetFaceInfoForEntity( mapent );
|
|
|
|
if( !Q_strcmp( token, "patchDef2" ))
|
|
{
|
|
// NOTE: patchDef may be combined with old brush descripton
|
|
ParsePatch( mapent, index, faceinfo, brush_type );
|
|
g_numparsedbrushes++;
|
|
}
|
|
else if( !Q_strcmp( token, "terrainDef" ))
|
|
{
|
|
MsgDev( D_REPORT, "terrains not supported, skipping terrain on line %d\n", scriptline );
|
|
SkipBracedSection( 0 );
|
|
}
|
|
else if( !Q_strcmp( token, "brushDef" ))
|
|
{
|
|
brush_type = BRUSH_RADIANT;
|
|
ParseBrush( mapent, index, faceinfo, brush_type ); // parse brush primitive
|
|
g_numparsedbrushes++;
|
|
}
|
|
else
|
|
{
|
|
// predict state
|
|
if( brush_type == BRUSH_UNKNOWN )
|
|
brush_type = BRUSH_WORLDCRAFT_21;
|
|
|
|
// QuArK or WorldCraft map
|
|
UnGetToken();
|
|
ParseBrush( mapent, index, faceinfo, brush_type );
|
|
g_numparsedbrushes++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e = ParseEpair ();
|
|
|
|
if( mapent->brushes.Count() > 0 )
|
|
MsgDev( D_WARN, "ParseMapEntity: keyvalue comes after brushes\n" ); //--vluzacn
|
|
|
|
if( !external && entities->Count() == 1 && !Q_strcmp( e->key, "zhlt_worldluxels" ))
|
|
g_world_luxels = atoi( e->value );
|
|
|
|
InsertLinkBefore( e, (entity_t *)mapent );
|
|
}
|
|
}
|
|
|
|
if( !external && entities->Count() == 1 )
|
|
{
|
|
// let the map tell which version of the compiler it comes from, to help tracing compiler bugs.
|
|
Q_snprintf( str, sizeof( str ), "%s (%s)", COMPILERSTRING, __DATE__ );
|
|
SetKeyValue( (entity_t *)mapent, "_compiler", str ); // g-cont. don't pass this field into game dlls
|
|
}
|
|
|
|
// save off to displaying the map format
|
|
if( g_brushtype == BRUSH_UNKNOWN )
|
|
g_brushtype = brush_type;
|
|
|
|
GetVectorForKey( (entity_t *)mapent, "origin", mapent->origin );
|
|
|
|
const char *classname = ValueForKey( (entity_t *)mapent, "classname" );
|
|
|
|
// group entities are just for editor convenience toss all brushes into the world entity
|
|
if( !Q_strcmp( "func_group", classname ) || !Q_strcmp( "func_landscape", classname ) || !Q_strncmp( "func_detail", classname, 11 ))
|
|
{
|
|
MoveBrushesToEntity( entities, &entities->Element( 0 ), mapent );
|
|
FreeMapEntity( mapent ); // throw all key-value pairs
|
|
entities->Remove( index );
|
|
|
|
return true;
|
|
}
|
|
|
|
// processing the external mapfile
|
|
if( !Q_strcmp( classname, "misc_model" ))
|
|
{
|
|
char modelpath[64];
|
|
|
|
Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "model" ), sizeof( modelpath ));
|
|
COM_ReplaceExtension( modelpath, ".mdl" );
|
|
|
|
if( FS_FileExists( modelpath, false ))
|
|
{
|
|
// keep this entity and convert into env_static
|
|
SetKeyValue((entity_t *)mapent, "classname", "env_static" );
|
|
SetKeyValue((entity_t *)mapent, "model", modelpath );
|
|
SetKeyValue((entity_t *)mapent, "spawnflags", "1" ); // TESTTEST
|
|
classname = ValueForKey( (entity_t *)mapent, "classname" ); // refresh the pointer
|
|
|
|
// rename some keys
|
|
if( CheckKey( (entity_t *)mapent, "modelscale_vec" ))
|
|
RenameKey( (entity_t *)mapent, "modelscale_vec", "xform" );
|
|
if( CheckKey( (entity_t *)mapent, "modelscale" ))
|
|
RenameKey( (entity_t *)mapent, "modelscale_vec", "scale" );
|
|
}
|
|
else
|
|
{
|
|
Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "model" ), sizeof( modelpath ));
|
|
IncludeMapFile( modelpath, entities, index, false );
|
|
FreeMapEntity( mapent ); // throw all key-value pairs
|
|
entities->Remove( index );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// processing the external mapfile Tyr-Utils (not recursive)
|
|
if( !external && !Q_strcmp( classname, "misc_external_map" ))
|
|
{
|
|
char modelpath[64];
|
|
|
|
Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "_external_map" ), sizeof( modelpath ));
|
|
|
|
if( FS_FileExists( modelpath, false ))
|
|
{
|
|
// keep this entity and convert into specified classname
|
|
SetKeyValue((entity_t *)mapent, "classname", ValueForKey((entity_t *)mapent, "_external_map_classname" ));
|
|
RemoveKey( (entity_t *)mapent, "_external_map_classname" );
|
|
RemoveKey( (entity_t *)mapent, "_external_map" );
|
|
classname = ValueForKey( (entity_t *)mapent, "classname" ); // refresh the pointer
|
|
|
|
IncludeMapFile( modelpath, entities, index, true );
|
|
|
|
// remove source keys
|
|
RemoveKey( (entity_t *)mapent, "_external_map_angles" );
|
|
RemoveKey( (entity_t *)mapent, "_external_map_angle" );
|
|
RemoveKey( (entity_t *)mapent, "_external_map_scale" );
|
|
RemoveKey( (entity_t *)mapent, "origin" );
|
|
}
|
|
else
|
|
{
|
|
// failed to loading external map
|
|
entities->Remove( index );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if( !external && !Q_strcmp( classname, "env_cubemap" ))
|
|
{
|
|
if( g_numcubemaps == MAX_MAP_CUBEMAPS )
|
|
COM_FatalError( "MAX_MAP_CUBEMAPS limit exceeded\n" );
|
|
|
|
// save off cubemap positions
|
|
dcubemap_t *pCubemap = &g_dcubemaps[g_numcubemaps];
|
|
pCubemap->origin[0] = (short)mapent->origin[0];
|
|
pCubemap->origin[1] = (short)mapent->origin[1];
|
|
pCubemap->origin[2] = (short)mapent->origin[2];
|
|
pCubemap->size = (short)IntForKey( (entity_t *)mapent, "cubemapsize" );
|
|
FreeMapEntity( mapent ); // throw all key-value pairs
|
|
entities->Remove( index );
|
|
g_numcubemaps++;
|
|
|
|
return true;
|
|
}
|
|
|
|
// auto-enum targets
|
|
if( !Q_strcmp( classname, "multi_switcher" ))
|
|
{
|
|
char string[32];
|
|
int count = 0;
|
|
|
|
for( e = mapent->epairs; e; e = e->next )
|
|
{
|
|
if( !Q_strcmp( e->key, "mode" ))
|
|
continue; // ignore mode
|
|
|
|
// only change if value is 0
|
|
if( Q_isdigit( e->value ) && !atoi( e->value ))
|
|
{
|
|
Q_snprintf( string, sizeof( string ), "%d", count );
|
|
freestring( e->value );
|
|
e->value = copystring( string );
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( fabs( mapent->origin[0] ) > WORLD_MAXS || fabs( mapent->origin[1] ) > WORLD_MAXS || fabs( mapent->origin[2] ) > WORLD_MAXS )
|
|
{
|
|
if( Q_strncmp( classname, "light", 5 ))
|
|
{
|
|
MsgDev( D_WARN, "Entity %i (classname \"%s\"): origin outside +/-%.0f: (%.0f,%.0f,%.0f)",
|
|
g_numparsedentities, classname, (double)WORLD_MAXS, mapent->origin[0], mapent->origin[1], mapent->origin[2] );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
IncludeMapFile
|
|
================
|
|
*/
|
|
bool IncludeMapFile( const char *filename, CUtlArray<mapent_t> *entities, int index, bool external_map )
|
|
{
|
|
mapent_t *mapent = &entities->Element( index );
|
|
const char *ext = COM_FileExtension( filename );
|
|
int oldentitynum = g_numparsedentities;
|
|
int oldbrushtype = g_brushtype;
|
|
mapent_t *localworld, *target;
|
|
CUtlArray<mapent_t> localents;
|
|
char path[256];
|
|
int i;
|
|
|
|
// only .ase and .map is allowed
|
|
if( Q_stricmp( ext, "ase" ) && Q_stricmp( ext, "map" ))
|
|
return false;
|
|
|
|
int spawnflags = IntForKey((entity_t *)mapent, "spawnflags" );
|
|
|
|
if( InsertASEModel( filename, mapent, index, -1 ))
|
|
{
|
|
MsgDev( D_INFO, "include: %s\n", filename );
|
|
|
|
for( int i = 0; i < mapent->brushes.Count(); i++ )
|
|
{
|
|
brush_t *b = &mapent->brushes[i];
|
|
|
|
if( !FBitSet( spawnflags, 2 ))
|
|
SetBits( b->flags, FBRUSH_NOCLIP );
|
|
SetBits( b->flags, FBRUSH_NOCSG );
|
|
}
|
|
|
|
// all the remaining brushes it our included model. transform it
|
|
EntityApplyTransform( mapent, mapent, true, false, -90.0 );
|
|
|
|
// find the corresponding entity which we send brushes from this model
|
|
target = FindTargetMapEntity( entities, ValueForKey((entity_t *)mapent, "target" ));
|
|
if( !target ) target = &entities->Element( 0 ); // move to world as default
|
|
|
|
// moving our transformed brushes into supposed entity
|
|
MoveBrushesToEntity( entities, target, mapent );
|
|
|
|
return true;
|
|
}
|
|
|
|
Q_strncpy( path, filename, sizeof( path ));
|
|
COM_ReplaceExtension( path, ".map" );
|
|
|
|
if( !FS_FileExists( path, false ))
|
|
return false;
|
|
|
|
// parse sub-map with models
|
|
IncludeScriptFile( path );
|
|
g_numparsedentities = g_numparsedbrushes = 0;
|
|
g_brushtype = BRUSH_UNKNOWN;
|
|
localents.Purge();
|
|
|
|
while( ParseMapEntity( &localents, true ))
|
|
{
|
|
g_numparsedentities++;
|
|
}
|
|
|
|
// deal only with world entity (all the misc_models should be recursive include into the worldentity)
|
|
localworld = &localents[0];
|
|
|
|
MsgDev( D_INFO, "include: %s ^2%s^7\n", path, g_sMapType[g_brushtype] );
|
|
MsgDev( D_REPORT, "\n" );
|
|
MsgDev( D_REPORT, "%5i brushes\n", localworld->brushes.Count());
|
|
MsgDev( D_REPORT, "%5i entities\n", localents.Count() );
|
|
restart:
|
|
if( !external_map )
|
|
{
|
|
// Quake3 style recursive-included prefabs (keep only detail brushes)
|
|
for( i = 0; i < localworld->brushes.Count(); i++ )
|
|
{
|
|
brush_t *b = &localworld->brushes[i];
|
|
|
|
// remove non-detail brushes, this is a debug stuff
|
|
if( b->detaillevel ) continue;
|
|
|
|
DeleteBrush( localworld, b );
|
|
goto restart; // array is changed, restart
|
|
}
|
|
|
|
for( i = 0; i < localworld->brushes.Count(); i++ )
|
|
{
|
|
brush_t *b = &localworld->brushes[i];
|
|
|
|
if( !FBitSet( spawnflags, 2 ))
|
|
SetBits( b->flags, FBRUSH_NOCLIP );
|
|
// SetBits( b->flags, FBRUSH_NOCSG );
|
|
}
|
|
}
|
|
|
|
mapent = &entities->Element( index ); // refresh the source pointer
|
|
// all the remaining brushes it our included model. transform it
|
|
EntityApplyTransform( mapent, localworld, true, external_map );
|
|
|
|
if( !external_map )
|
|
{
|
|
// find the corresponding entity which we send brushes from this model
|
|
target = FindTargetMapEntity( entities, ValueForKey((entity_t *)mapent, "target" ));
|
|
if( !target ) target = &entities->Element( 0 ); // move to world as default
|
|
}
|
|
else
|
|
{
|
|
// target is himself
|
|
target = mapent;
|
|
}
|
|
|
|
// moving our transformed brushes into supposed entity
|
|
MoveBrushesToEntity( &localents, target, localworld );
|
|
|
|
// move env_static from sub-levels
|
|
for( i = 0; i < localents.Count(); i++ )
|
|
{
|
|
mapent_t *ent = &localents[i];
|
|
|
|
if( Q_strcmp( ValueForKey( (entity_t *)ent, "classname" ), "env_static" ))
|
|
continue;
|
|
|
|
mapent = &entities->Element( index ); // refresh the source pointer
|
|
EntityApplyTransform( mapent, ent, false, false );
|
|
|
|
// copy point entity into the parent pool
|
|
int newidx = AllocEntity( entities );
|
|
mapent_t *newent = &entities->Element( newidx );
|
|
memcpy( newent, ent, sizeof( mapent_t ));
|
|
newent->epairs = newent->tail = NULL;
|
|
CopyEpairs( (entity_t *)newent, (entity_t *)ent );
|
|
}
|
|
|
|
// free any remaining ents
|
|
for( i = 0; i < localents.Count(); i++ )
|
|
FreeMapEntity( &localents[i] );
|
|
localents.Purge();
|
|
|
|
// all done, restore oldcount
|
|
g_numparsedentities = oldentitynum;
|
|
g_brushtype = oldbrushtype;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
FilterBrushesIntoArray
|
|
================
|
|
*/
|
|
void FilterBrushesIntoArray( mapent_t *mapent, int entnum, int contents )
|
|
{
|
|
// simple version of clip-nazi
|
|
const char *classname = ValueForKey( (entity_t *)mapent, "classname" );
|
|
bool noclip = !Q_stricmp( classname, "func_illusionary" );
|
|
|
|
for( int j = 0; j < mapent->brushes.Count(); j++ )
|
|
{
|
|
brush_t *dst = &g_mapbrushes[g_nummapbrushes];
|
|
brush_t *src = &mapent->brushes[j];
|
|
|
|
if( noclip ) SetBits( src->flags, FBRUSH_NOCLIP );
|
|
|
|
// update contents for nonclipped brushes
|
|
if( FBitSet( src->flags, FBRUSH_NOCLIP ))
|
|
{
|
|
for( int k = 0; k < src->sides.Count(); k++ )
|
|
{
|
|
side_t *s = &src->sides[k];
|
|
if( FBitSet( s->flags, FSIDE_NODRAW ))
|
|
{
|
|
Q_strncpy( s->name, "SOLIDHINT", sizeof( s->name ));
|
|
SetBits( s->flags, FSIDE_SOLIDHINT );
|
|
}
|
|
}
|
|
// g-cont. This causes problems. Disabled since 13.03.2019
|
|
// src->contents = CONTENTS_EMPTY;
|
|
}
|
|
|
|
if( src->contents != contents )
|
|
continue;
|
|
|
|
memcpy( dst, src, sizeof( brush_t ));
|
|
memset( src, 0, sizeof( brush_t ));
|
|
dst->entitynum = entnum;
|
|
mapent->numbrushes++;
|
|
g_nummapbrushes++;
|
|
|
|
// make sure what we not overflowed
|
|
if( g_nummapbrushes == MAX_MAP_BRUSHES )
|
|
COM_FatalError( "MAX_MAP_BRUSHES limit exceeded\n" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
PrepareMapBrushes
|
|
================
|
|
*/
|
|
void PrepareMapBrushes( void )
|
|
{
|
|
g_nummapbrushes = 0;
|
|
|
|
// move local brushes into single solid array
|
|
for( int i = 0; i < g_mapentities.Count(); i++ )
|
|
{
|
|
mapent_t *mapent = &g_mapentities[i];
|
|
|
|
mapent->firstbrush = g_nummapbrushes;
|
|
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_EMPTY );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_FOG );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_WATER );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_SLIME );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_LAVA );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_TRANSLUCENT );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_SKY );
|
|
FilterBrushesIntoArray( mapent, i, CONTENTS_SOLID ); // must be last
|
|
|
|
// release localcopy we doesn't need them
|
|
mapent->brushes.Purge();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
LoadMapFile
|
|
================
|
|
*/
|
|
void LoadMapFile( const char *filename )
|
|
{
|
|
LoadScriptFile( filename );
|
|
g_numparsedentities = 0;
|
|
g_mapentities.Purge();
|
|
|
|
while( ParseMapEntity( &g_mapentities ))
|
|
{
|
|
g_numparsedentities++;
|
|
}
|
|
|
|
Msg( "LoadMapFile: ^2%s^7\n\n", g_sMapType[g_brushtype] );
|
|
|
|
// now copy all the brushes into solid array
|
|
PrepareMapBrushes();
|
|
|
|
// at this point all the brushes stored into global array
|
|
MsgDev( D_INFO, "%5i brushes\n", g_nummapbrushes );
|
|
MsgDev( D_INFO, "%5i entities\n", g_mapentities.Count() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
MapEntityForModel
|
|
|
|
returns entity addy for given modelnum
|
|
================
|
|
*/
|
|
mapent_t *MapEntityForModel( const int modnum )
|
|
{
|
|
char name[16];
|
|
|
|
Q_snprintf( name, sizeof( name ), "*%i", modnum );
|
|
|
|
// search the entities for one using modnum
|
|
for( int i = 0; i < g_mapentities.Count(); i++ )
|
|
{
|
|
const char *s = ValueForKey((entity_t *)&g_mapentities[i], "model" );
|
|
if( !Q_strcmp( s, name )) return &g_mapentities[i];
|
|
}
|
|
|
|
return &g_mapentities[0];
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FindTargetEntity
|
|
==================
|
|
*/
|
|
mapent_t *FindTargetMapEntity( CUtlArray<mapent_t> *entities, const char *target )
|
|
{
|
|
for( int i = 0; i < entities->Count(); i++ )
|
|
{
|
|
char *n = ValueForKey( (entity_t *)&entities->Element( i ), "targetname" );
|
|
if( !Q_strcmp( n, target ))
|
|
return &entities->Element( i );
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
UnparseMapEntities
|
|
|
|
Generates the dentdata string from all the entities
|
|
================
|
|
*/
|
|
void UnparseMapEntities( void )
|
|
{
|
|
char *buf, *end;
|
|
char line[2048];
|
|
char key[1024], value[1024];
|
|
epair_t *ep;
|
|
|
|
buf = g_dentdata;
|
|
end = buf;
|
|
*end = 0;
|
|
|
|
for( int i = 0; i < g_mapentities.Count(); i++ )
|
|
{
|
|
mapent_t *ent = &g_mapentities[i];
|
|
|
|
if( !ent->epairs )
|
|
continue; // ent got removed
|
|
|
|
Q_strcat( end, "{\n" );
|
|
end += 2;
|
|
|
|
// ugly hack to put spawnorigin after origin pt.1
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( !Q_stricmp( ep->key, "spawnorigin" ))
|
|
continue;
|
|
|
|
Q_strncpy( key, ep->key, sizeof( key ));
|
|
StripTrailing( key );
|
|
Q_strncpy( value, ep->value, sizeof( value ));
|
|
StripTrailing( value );
|
|
|
|
Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value );
|
|
Q_strcat( end, line );
|
|
end += Q_strlen( line );
|
|
}
|
|
|
|
// ugly hack to put spawnorigin after origin pt.2
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( Q_stricmp( ep->key, "spawnorigin" ))
|
|
continue;
|
|
|
|
Q_strncpy( key, ep->key, sizeof( key ));
|
|
StripTrailing( key );
|
|
Q_strncpy( value, ep->value, sizeof( value ));
|
|
StripTrailing( value );
|
|
|
|
Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value );
|
|
Q_strcat( end, line );
|
|
end += Q_strlen( line );
|
|
}
|
|
|
|
Q_strcat( end, "}\n" );
|
|
end += 2;
|
|
|
|
if( end > ( buf + MAX_MAP_ENTSTRING ))
|
|
COM_FatalError( "Entity text too long\n" );
|
|
}
|
|
|
|
g_entdatasize = end - buf + 1; // write term
|
|
}
|
|
|
|
/*
|
|
================
|
|
FreeMapEntity
|
|
|
|
release all the entity data
|
|
================
|
|
*/
|
|
void FreeMapEntity( mapent_t *mapent )
|
|
{
|
|
epair_t *ep, *next;
|
|
|
|
for( ep = mapent->epairs; ep != NULL; ep = next )
|
|
{
|
|
next = ep->next;
|
|
freestring( ep->key );
|
|
freestring( ep->value );
|
|
Mem_Free( ep, C_EPAIR );
|
|
}
|
|
|
|
mapent->epairs = mapent->tail = NULL;
|
|
|
|
// release brushsides
|
|
for( int i = 0; i < mapent->brushes.Count(); i++ )
|
|
mapent->brushes[i].sides.Purge();
|
|
|
|
// release brushes
|
|
mapent->brushes.Purge();
|
|
}
|
|
|
|
/*
|
|
================
|
|
FreeMapEntities
|
|
|
|
release all the dynamically allocated data
|
|
================
|
|
*/
|
|
void FreeMapEntities( void )
|
|
{
|
|
for( int i = 0; i < g_mapentities.Count(); i++ )
|
|
{
|
|
FreeMapEntity( &g_mapentities[i] );
|
|
}
|
|
|
|
// remove all the entities
|
|
g_mapentities.Purge();
|
|
} |