Ejemplo n.º 1
0
void CheckboardTexture::Deserialization( tinyxml2::XMLElement* RootElement )
{
	Spectrum value;

	ParseVector( RootElement->Attribute( "value1" ) , value.GetDataPtr() );
	texture1 = new ConstantTexture( value );

	ParseVector( RootElement->Attribute( "value2" ) , value.GetDataPtr() );
	texture2 = new ConstantTexture( value );
}
Ejemplo n.º 2
0
void CheckboardTexture::Deserialization( tinyxml2::XMLElement* RootElement )
{
	Vector3f value;

	ParseVector( RootElement->Attribute( "value1" ) , &value[0] );
	texture1 = new ConstantTexture( value );

	ParseVector( RootElement->Attribute( "value2" ) , &value[0] );
	texture2 = new ConstantTexture( value );
}
bool CRainSystem::Command(const char *command)
{
	char	*token;

	if (CWorldEffectsSystem::Command(command))
	{
		return true;
	}

	token = COM_ParseExt(&command, false);

	if (strcmpi(token, "fog") == 0)
	{	// rain fog
		AddWorldEffect(new CMistyFog2);
		mWindChange = 0;
		return true;
	}
	else if (strcmpi(token, "fall") == 0)
	{	// rain fall ( minVelocity maxVelocity )			default: ( -60 -50 )
		float	data[2];

		if (ParseVector(&command, 2, data))
		{
			mMinVelocity[2] = data[0];
			mMaxVelocity[2] = data[1];
		}
		return true;
	}
	else if (strcmpi(token, "spread") == 0)
	{	// rain spread ( radius height )					default: ( 20 20 )
		ParseVector(&command, 2, &mSpread[1]);
		return true;
	}
	else if (strcmpi(token, "alpha") == 0)
	{	// rain alpha <float>								default: 0.15
		token = COM_ParseExt(&command, false);
		mAlpha = atof(token);
		return true;
	}
	else if (strcmpi(token, "height") == 0)
	{	// rain height <float>								default: 1.5
		token = COM_ParseExt(&command, false);
		mRainHeight = atof(token);
		return true;
	}
	else if (strcmpi(token, "angle") == 0)
	{	// rain angle <float>								default: 1.0
		token = COM_ParseExt(&command, false);
		mWindAngle = atof(token);
		return true;
	}

	return false;
}
bool FDefaultValueHelper::ParseRotator(const FString& Source, FRotator& OutVal)
{
	FVector Vector;
	if( ParseVector( Source, Vector ) )
	{
		OutVal = FRotator(Vector.X, Vector.Y, Vector.Z);
		return true;
	}
	return false;
}
Ejemplo n.º 5
0
void Sphere::Deserialization( tinyxml2::XMLElement* ShapeRootElement )
{
	ShapeRootElement->QueryFloatAttribute( "radius" , &m_Radius );

	ParseVector( std::string( ShapeRootElement->FirstChildElement( "transform" )->Attribute( "position" ) ) , &mWorldPos[0] );

	*mObjectToWorld = Translate( Vector3f( mWorldPos ) );
	*mWorldToObject = Inverse( *mObjectToWorld );

	BBoxLocal = Bound3f( Point3f( -m_Radius , -m_Radius , -m_Radius ) , Point3f( m_Radius , m_Radius , m_Radius ) );

	BBoxWorld = ( *mObjectToWorld )( BBoxLocal );
}
Ejemplo n.º 6
0
//------------------------------------------------------
// ParseVelocity
//	Reads in a ranged velocity field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseVelocity( const gsl::cstring_view& val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mVelX.SetRange( min[0], max[0] );
		mVelY.SetRange( min[1], max[1] );
		mVelZ.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 7
0
//------------------------------------------------------
// ParseOrigin2
//	Reads in an origin field in vector format
//
// input:
//	string that contains three float values
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseOrigin2( const char *val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mOrigin2X.SetRange( min[0], max[0] );
		mOrigin2Y.SetRange( min[1], max[1] );
		mOrigin2Z.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 8
0
//------------------------------------------------------
// ParseAngleDelta
//	Reads in a ranged angleDelta field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseAngleDelta( const char *val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mAngle1Delta.SetRange( min[0], max[0] );
		mAngle2Delta.SetRange( min[1], max[1] );
		mAngle3Delta.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 9
0
//------------------------------------------------------
// ParseAcceleration
//	Reads in a ranged acceleration field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseAcceleration( const char *val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mAccelX.SetRange( min[0], max[0] );
		mAccelY.SetRange( min[1], max[1] );
		mAccelZ.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 10
0
//------------------------------------------------------
// ParseRGBEnd
//	Reads in a ranged rgbEnd field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseRGBEnd( const char *val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mRedEnd.SetRange( min[0], max[0] );
		mGreenEnd.SetRange( min[1], max[1] );
		mBlueEnd.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 11
0
//------------------------------------------------------
// ParseRGBStart
//	Reads in a ranged rgbStart field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseRGBStart( const gsl::cstring_view& val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mRedStart.SetRange( min[0], max[0] );
		mGreenStart.SetRange( min[1], max[1] );
		mBlueStart.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 12
0
//------------------------------------------------------
// ParseAngle
//	Reads in a ranged angle field in vector format
//
// input:
//	string that contains one or two vectors
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseAngle( const gsl::cstring_view& val )
{
	vec3_t min, max;

	if ( ParseVector( val, min, max ) == true )
	{
		mAngle1.SetRange( min[0], max[0] );
		mAngle2.SetRange( min[1], max[1] );
		mAngle3.SetRange( min[2], max[2] );
		return true;
	}

	return false;
}
Ejemplo n.º 13
0
//------------------------------------------------------
// ParseMin
//	Reads in a min bounding box field in vector format
//
// input:
//	string that contains three float values
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseMin( const gsl::cstring_view& val )
{
	vec3_t min;

	if ( ParseVector( val, min, min ) == true )
	{
		VectorCopy( min, mMin );

		// We assume that if a min is being set that we are using physics and a bounding box
		mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS);
		return true;
	}

	return false;
}
Ejemplo n.º 14
0
//------------------------------------------------------
// ParseMax
//	Reads in a max bounding box field in vector format
//
// input:
//	string that contains three float values
//
// return:
//	success of parse operation.
//------------------------------------------------------
bool CPrimitiveTemplate::ParseMax( const char *val )
{
	vec3_t max;

	if ( ParseVector( val, max, max ) == true )
	{
		VectorCopy( max, mMax );

		// We assume that if a max is being set that we are using physics and a bounding box
		mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS);
		return true;
	}

	return false;
}
Ejemplo n.º 15
0
Vector2 ParseVertex(const picojson::value& vertices, const picojson::value& id) {
  const auto& vertex = vertices.get(id.to_str());
  const auto& position = vertex.get("Position");
  return ParseVector(position);
}
Ejemplo n.º 16
0
void AreaLight::Deserialization( tinyxml2::XMLElement* LightRootElement )
{
	ParseVector( LightRootElement->Attribute( "Le" ) , Lemission.GetDataPtr() );
}
Ejemplo n.º 17
0
bool CSnowSystem::Command(const char *command)
{
	char	*token;

	if (CWorldEffectsSystem::Command(command))
	{
		return true;
	}

	token = COM_ParseExt(&command, false);

	if (strcmpi(token, "wind") == 0)
	{	// snow wind ( windOriginX windOriginY windOriginZ ) ( windVelocityX windVelocityY windVelocityZ ) ( sizeX sizeY sizeZ )
		vec3_t	origin, velocity, size;

		ParseVector(&command, 3, origin);
		ParseVector(&command, 3, velocity);
		ParseVector(&command, 3, size);

		AddWorldEffect(new CWind(origin, velocity, size, 0));

		return true;
	}
	else if (strcmpi(token, "fog") == 0)
	{	// snow fog
		AddWorldEffect(new CMistyFog2);
		mWindChange = 0;
		return true;
	}
	else if (strcmpi(token, "alpha") == 0)
	{	// snow alpha <float>											default: 0.09
		token = COM_ParseExt(&command, false);
		mAlpha = atof(token);
		return true;
	}
	else if (strcmpi(token, "spread") == 0)
	{	// snow spread ( minX minY minZ ) ( maxX maxY maxZ )			default: ( -600 -600 -200 ) ( 600 600 250 )
		ParseVector(&command, 3, mMinSpread);
		ParseVector(&command, 3, mMaxSpread);
		return true;
	}
	else if (strcmpi(token, "velocity") == 0)
	{	// snow velocity ( minX minY minZ ) ( maxX maxY maxZ )			default: ( -15 -15 -20 ) ( 15 15 -70 )
		ParseVector(&command, 3, mMinSpread);
		ParseVector(&command, 3, mMaxSpread);
		return true;
	}
	else if (strcmpi(token, "blowing") == 0)
	{	
		token = COM_ParseExt(&command, false);
		if (strcmpi(token, "duration") == 0)
		{	// snow blowing duration <int>									default: 2
			token = COM_ParseExt(&command, false);
			mWindDuration = atol(token);
			return true;
		}
		else if (strcmpi(token, "low") == 0)
		{	// snow blowing low <int>										default: 3
			token = COM_ParseExt(&command, false);
			mWindLow = atol(token);
			return true;
		}
		else if (strcmpi(token, "velocity") == 0)
		{	// snow blowing velocity ( min max )							default: ( 30 70 )
			float	data[2];

			ParseVector(&command, 2, data);
			mWindMin = data[0];
			mWindMax = data[1];
			return true;
		}
		else if (strcmpi(token, "size") == 0)
		{	// snow blowing size ( minX minY minZ )							default: ( 1000 300 300 )
			ParseVector(&command, 3, mWindSize);
			return true;
		}
	}

	return false;
}
Ejemplo n.º 18
0
void Sphere::Deserialization( tinyxml2::XMLElement* ShapeRootElement )
{
	ShapeRootElement->QueryFloatAttribute( "radius" , &mRadius );

	ParseVector( std::string( ShapeRootElement->FirstChildElement( "transform" )->Attribute( "position" ) ) , &mWorldPos[0] );
}
Ejemplo n.º 19
0
/*
================
Model::ImportMD5
================
*/
Model *Model::ImportMD5( const char *filename, const char *filenameAnim ) {
    Lexer lexer(LEXER_NO_BOM_WARNING);
    if ( !lexer.LoadFile( filename ) )
        return NULL;

    Model *model = new Model(true);
    int numJoints = -1;
    try {

        // MD5Version must be the first keyword
        lexer.ExpectToken( "MD5Version" );
        int fileVersion = lexer.ReadInt();
        if ( fileVersion != MD5_VERSION_DOOM3 && fileVersion != MD5_VERSION_QUAKEWARS )
            lexer.Error( Format( "MD5Version is $*, should be $* or $*") << fileVersion << MD5_VERSION_DOOM3 << MD5_VERSION_QUAKEWARS );

        const Token *token;
        String key;

        const char *p;

        bool inJointGroup = false;
        bool inMeshGroup = false;
        bool readMeshVerts = false;
        bool readMeshTris = false;

        int numMeshes = -1;
        int numVerts, vertIndex;
        int triCounter, indexCounter;
        int numWeights;
        MeshAnimated *mesh = NULL;

        int i, j, num;
        Vec3 vTemp;

        bool noAnimate = false;
        bool readVertexColor = false;
        Color vertexColor;

        ListEx<MD5Vertex> vertexList;
        ListEx<MD5Weight> weightList;

        while ( (token = lexer.ReadToken()) != NULL ) {
            p = token->GetString();
            if ( !p || !*p )
                continue;

            if ( inJointGroup ) {
                if ( *p == '}' ) {
                    inJointGroup = false;
                    continue;
                }
                lexer.ReadInt(); // unused
                ParseVector( lexer, &vTemp.x, 3 );
                ParseVector( lexer, &vTemp.x, 3 );
            } else if ( inMeshGroup ) {
                lexer.UnreadToken();
                if ( readMeshVerts ) {
                    if ( lexer.CheckToken( "numtris" ) ) {
                        if ( numVerts != vertexList.Num() )
                            lexer.Error( Format("numVerts don't match: $*") << vertexList.Num() );
                        InitIndices( mesh, lexer.ReadInt() * 3 ); // numtris * 3
                        triCounter = 0;
                        indexCounter = 0;
                        readMeshVerts = false;
                        readMeshTris = true;
                        continue;
                    }
                    lexer.ExpectToken( "vert" );

                    vertIndex = lexer.ReadInt();
                    if ( vertIndex != vertexList.Num() )
                        lexer.Error( Format("Bad Vert Index, should be $*") << vertexList.Num() );

                    MD5Vertex &vertex = vertexList.Alloc();
                    ParseVector( lexer, &vertex.texCoord.x, 2 );
                    vertex.firstWeight = lexer.ReadInt();
                    vertex.lastWeight = lexer.ReadInt() + vertex.firstWeight - 1;

                    if ( readVertexColor ) {
                        // Currently we don't have use for vertex color..
                        // I haven't seen md5meshes with other values than ( 1 1 1 1 ) for vertexColor anyway..
                        ParseVector( lexer, &vertexColor.r, 4 );
                    }
                } else if ( readMeshTris ) {
                    if ( lexer.CheckToken( "numweights" ) ) {
                        numWeights = lexer.ReadInt();
                        weightList.CheckSize( numWeights );
                        readMeshTris = false;
                        continue;
                    }
                    lexer.ExpectToken( "tri" );

                    if ( lexer.ReadInt() != triCounter )
                        lexer.Error( Format("Bad Tri Index, should be $*") << triCounter );

                    mesh->indices[indexCounter++] = lexer.ReadInt();
                    mesh->indices[indexCounter++] = lexer.ReadInt();
                    mesh->indices[indexCounter++] = lexer.ReadInt();
                    triCounter++;
                } else {
                    if ( lexer.CheckToken( "}" ) ) {
                        if ( numWeights != weightList.Num() )
                            lexer.Error( Format("numWeights doesn't match $*") << weightList.Num() );

                        // Convert vertices:
                        num = vertexList.Num();
                        for( i=0; i<num; i++ ) {
                            if ( i >= vertexList.Num() )
                                lexer.Error( "Vertex index out of range" );

                            const MD5Vertex &md5Vert = vertexList[i];

                            mesh->texCoords[i] = md5Vert.texCoord;

                            Vertex &vInfo = mesh->vertices[i];
                            InitVertex( &vInfo, 1 + md5Vert.lastWeight - md5Vert.firstWeight );
                            vInfo.numWeights = 0;

                            for ( j=md5Vert.firstWeight; j<=md5Vert.lastWeight; j++ ) {
                                if ( j >= weightList.Num() )
                                    lexer.Error( "Weight index out of range" );
                                const MD5Weight &md5Weight = weightList[j];

                                if ( md5Weight.jointIndex >= numJoints )
                                    lexer.Error( "Weight joint index out of range" );

                                if ( md5Weight.bias == 0.0f )
                                    continue;

                                VertexWeight &weight = vInfo.weights[vInfo.numWeights++];
                                weight.boneId = md5Weight.jointIndex;
                                weight.origin = md5Weight.origin;
                                //weight.normal = md5Weight.normal; //! @todo	calculate normal
                                weight.influence = md5Weight.bias;
                            }
                        }
                        inMeshGroup = false;
                        continue;
                    }
                    lexer.ExpectToken( "weight" );

                    if ( lexer.ReadInt() != weightList.Num() )
                        lexer.Error( Format("Bad VertexWeight Index, should be $*") << weightList.Num() );

                    MD5Weight &weight = weightList.Alloc();
                    weight.jointIndex = lexer.ReadInt();
                    weight.bias = lexer.ReadFloat();
                    ParseVector( lexer, &weight.origin.x, 3 );
                    weight.origin *= MD5_MODEL_SCALE;
                }
            } else {
                if ( String::Icmp( p, "commandline" ) == 0 ) {
                    // Skip value, we don't need it
                    lexer.ReadToken();
                } else if ( String::Icmp( p, "numJoints" ) == 0 ) {
                    numJoints = lexer.ReadInt();
                    if ( numJoints > 0 ) {
                        model->bones.SetGranularity( numJoints );
                        model->bones.CheckSize( numJoints );
                    }
                } else if ( String::Icmp( p, "numMeshes" ) == 0 ) {
                    numMeshes = lexer.ReadInt();
                    if ( numMeshes <= 0 )
                        lexer.Error("Zero meshes");
                    model->meshes.SetGranularity( numMeshes );
                    model->meshes.CheckSize( numMeshes );
                } else if ( String::Icmp( p, "joints" ) == 0 ) {
                    if ( numJoints == -1 )
                        lexer.Error( "numJoins not set!" );

                    lexer.ExpectToken( "{" );
                    inJointGroup = true;
                } else if ( String::Icmp( p, "mesh" ) == 0 ) {
                    if ( numMeshes == -1 )
                        lexer.Error( "numMeshes not set!" );

                    lexer.ExpectToken( "{" );

                    mesh = new MeshAnimated;
                    model->meshes.Append(mesh);

                    if ( fileVersion != MD5_VERSION_QUAKEWARS )
                        mesh->name = Format("mesh $*") << model->meshes.Num();
                    else {
                        lexer.ExpectToken( "name" );
                        mesh->name = lexer.ReadString();
                    }

                    lexer.ExpectToken( "shader" );
                    mesh->material = lexer.ReadString();

                    noAnimate = false;
                    readVertexColor = false;
                    if ( fileVersion == MD5_VERSION_QUAKEWARS ) {
                        lexer.ExpectToken( "flags" );
                        lexer.ExpectToken( "{" );
                        while( !lexer.CheckToken("}") ) {
                            if ( lexer.CheckToken("noAnimate") )
                                noAnimate = true;
                            else if ( lexer.CheckToken("vertexColor") )
                                readVertexColor = true;
                            else
                                lexer.Error( Format("Unknown flag: '$*'") << lexer.ReadString() );
                        }
                    }
                    lexer.ExpectToken( "numverts" );

                    numVerts = lexer.ReadInt();

                    vertexList.Clear();
                    vertexList.CheckSize( numVerts );
                    weightList.Clear();
                    InitVertices( mesh, numVerts );
                    inMeshGroup = true;
                    readMeshVerts = true;
                } else {
                    lexer.Error( Format("Unexpected '$*'") << p );
                }
            }
        }
        if ( inJointGroup || inMeshGroup || numJoints == -1 || numMeshes == -1 )
            throw LexerError( LexerError::END_OF_FILE );
    }
    catch( LexerError err ) {
        delete model;
        String errStr;
        err.ToString( errStr );
        User::Error( ERR_LEXER_FAILURE, errStr.c_str(), filename );
        return NULL;
    }

    if ( !ImportMD5AnimBaseFrame( filenameAnim, model ) )
        return NULL;

    if ( numJoints != model->bones.Num() ) {
        delete model;
        User::Error( ERR_FILE_CORRUPT, "Number of Joints do not match", filename );
        return NULL;
    }
    return model;
}
Ejemplo n.º 20
0
/*
============
idAASSettings::FromParser
============
*/
bool idAASSettings::FromParser( idLexer &src )
{
    idToken token;

    if ( !src.ExpectTokenString( "{" ) )
    {
        return false;
    }

    // parse the file
    while ( 1 )
    {
        if ( !src.ReadToken( &token ) )
        {
            break;
        }

        if ( token == "}" )
        {
            break;
        }

        if ( token == "bboxes" )
        {
            if ( !ParseBBoxes( src ) )
            {
                return false;
            }
        }
        else if ( token == "usePatches" )
        {
            if ( !ParseBool( src, usePatches ) )
            {
                return false;
            }
        }
        else if ( token == "writeBrushMap" )
        {
            if ( !ParseBool( src, writeBrushMap ) )
            {
                return false;
            }
        }
        else if ( token == "playerFlood" )
        {
            if ( !ParseBool( src, playerFlood ) )
            {
                return false;
            }
        }
        else if ( token == "allowSwimReachabilities" )
        {
            if ( !ParseBool( src, allowSwimReachabilities ) )
            {
                return false;
            }
        }
        else if ( token == "allowFlyReachabilities" )
        {
            if ( !ParseBool( src, allowFlyReachabilities ) )
            {
                return false;
            }
        }
        else if ( token == "fileExtension" )
        {
            src.ExpectTokenString( "=" );
            src.ExpectTokenType( TT_STRING, 0, &token );
            fileExtension = token;
        }
        else if ( token == "gravity" )
        {
            ParseVector( src, gravity );
            gravityDir = gravity;
            gravityValue = gravityDir.Normalize();
            invGravityDir = -gravityDir;
        }
        else if ( token == "maxStepHeight" )
        {
            if ( !ParseFloat( src, maxStepHeight ) )
            {
                return false;
            }
        }
        else if ( token == "maxBarrierHeight" )
        {
            if ( !ParseFloat( src, maxBarrierHeight ) )
            {
                return false;
            }
        }
        else if ( token == "maxWaterJumpHeight" )
        {
            if ( !ParseFloat( src, maxWaterJumpHeight ) )
            {
                return false;
            }
        }
        else if ( token == "maxFallHeight" )
        {
            if ( !ParseFloat( src, maxFallHeight ) )
            {
                return false;
            }
        }
        else if ( token == "minFloorCos" )
        {
            if ( !ParseFloat( src, minFloorCos ) )
            {
                return false;
            }
        }
        else if ( token == "tt_barrierJump" )
        {
            if ( !ParseInt( src, tt_barrierJump ) )
            {
                return false;
            }
        }
        else if ( token == "tt_startCrouching" )
        {
            if ( !ParseInt( src, tt_startCrouching ) )
            {
                return false;
            }
        }
        else if ( token == "tt_waterJump" )
        {
            if ( !ParseInt( src, tt_waterJump ) )
            {
                return false;
            }
        }
        else if ( token == "tt_startWalkOffLedge" )
        {
            if ( !ParseInt( src, tt_startWalkOffLedge ) )
            {
                return false;
            }
        }
        else
        {
            src.Error( "invalid token '%s'", token.c_str() );
        }
    }

    if ( numBoundingBoxes <= 0 )
    {
        src.Error( "no valid bounding box" );
    }

    return true;
}
Ejemplo n.º 21
0
/*
================
ImportMD5AnimBaseFrame
================
*/
bool ImportMD5AnimBaseFrame( const char *filename, Model *model ) {
    Lexer lexer(LEXER_NO_BOM_WARNING);
    if ( !lexer.LoadFile( filename ) ) {
        delete model;
        return false;
    }

    try {
        // MD5Version must be the first keyword
        lexer.ExpectToken( "MD5Version" );
        int fileVersion = lexer.ReadInt();
        if ( fileVersion != MD5_VERSION_DOOM3 && fileVersion != MD5_VERSION_QUAKEWARS )
            lexer.Error( Format("MD5Version is $*, should be $* or $*") << fileVersion << MD5_VERSION_DOOM3 << MD5_VERSION_QUAKEWARS );

        const Token *token;
        String key;

        const char *p;

        bool inHierarchyGroup = false;
        bool inBoundsGroup = false;
        bool inBaseFrameGroup = false;
        bool inFrameGroup = false;

        int numJoints = -1;
        int frameRate, numFrames, currentFrame;

        bool noAnimate = false;
        bool readVertexColor = false;
        Color vertexColor;

        ListEx<MD5Vertex> vertexList;
        ListEx<MD5Weight> weightList;
        Vec3 vTemp;
        int boneIndex = 0;

        while ( (token = lexer.ReadToken()) != NULL ) {
            p = token->GetString();
            if ( !p || !*p )
                continue;

            if ( inHierarchyGroup ) {
                if ( *p == '}' ) {
                    inHierarchyGroup = false;
                    continue;
                }
                Bone &bone = model->bones.Alloc();
                bone.name = p;
                bone.idParent = lexer.ReadInt();
                bone.flags = lexer.ReadInt();
                lexer.ReadInt(); // start index ?
            } else if ( inBaseFrameGroup ) {
                if ( *p == '}' ) {
                    inBaseFrameGroup = false;
                    break; //! @todo	should be continue when loading all frames.
                }
                lexer.UnreadToken();
                Bone &bone = model->bones[boneIndex++];
                ParseVector( lexer, &bone.origin.x, 3 );
                ParseVector( lexer, &bone.quat.x, 3 );

                bone.origin *= MD5_MODEL_SCALE;

                // Compute w
                bone.quat.w = 1.0f - (bone.quat.x * bone.quat.x) - (bone.quat.y * bone.quat.y) - (bone.quat.z * bone.quat.z);
                bone.quat.w = ( bone.quat.w <= 0.0f ) ? 0.0f : -Math::Sqrt( bone.quat.w );
            } else if ( inBoundsGroup ) {
                if ( *p == '}' ) {
                    inBoundsGroup = false;
                    continue;
                }
                lexer.UnreadToken();
                ParseVector( lexer, &vTemp.x, 3 );
                ParseVector( lexer, &vTemp.x, 3 );
            } else if ( inFrameGroup ) {
                if ( *p == '}' ) {
                    inFrameGroup = false;
                    continue;
                }
                //! @todo	why was here a fixme ?
            } else {
                if ( String::Icmp( p, "commandline" ) == 0 ) {
                    // Skip value, we don't need it
                    lexer.ReadToken();
                } else if ( String::Icmp( p, "frameRate" ) == 0 ) {
                    frameRate = lexer.ReadInt();
                } else if ( String::Icmp( p, "numAnimatedComponents" ) == 0 ) {
                    // Skip value, we don't need it
                    lexer.ReadToken();
                } else if ( String::Icmp( p, "numJoints" ) == 0 ) {
                    numJoints = lexer.ReadInt();
                    if ( numJoints > 0 ) {
                        model->bones.SetGranularity( numJoints );
                        model->bones.CheckSize( numJoints );
                    }
                } else if ( String::Icmp( p, "numFrames" ) == 0 ) {
                    numFrames = lexer.ReadInt();
                    if ( numFrames <= 0 )
                        lexer.Error("Zero frames on model");
                    //! @todo	why was here a fixme ?
                } else if ( String::Icmp( p, "hierarchy" ) == 0 ) {
                    if ( numJoints == -1 )
                        lexer.Error("numJoins not set!");
                    lexer.ExpectToken( "{" );
                    inHierarchyGroup = true;
                } else if ( String::Icmp( p, "bounds" ) == 0 ) {
                    lexer.ExpectToken( "{" );
                    inBoundsGroup = true;
                } else if ( String::Icmp( p, "baseframe" ) == 0 ) {
                    lexer.ExpectToken( "{" );
                    inBaseFrameGroup = true;
                } else if ( String::Icmp( p, "frame" ) == 0 ) {
                    currentFrame = lexer.ReadInt();
                    lexer.ExpectToken( "{" );
                    inFrameGroup = true;
                } else {
                    lexer.Error( Format("Unexpected '$*'") << p );
                }
            }
        }
        if ( inHierarchyGroup || inBoundsGroup || inFrameGroup || numJoints == -1 )
            throw LexerError( LexerError::END_OF_FILE );
        return true;
    }
    catch( LexerError err ) {
        delete model;
        String errStr;
        err.ToString( errStr );
        User::Error( ERR_LEXER_FAILURE, errStr.c_str(), filename );
        return false;
    }
}
Ejemplo n.º 22
0
/**
 * @brief The current text pointer is at the explicit text definition of the
 * shader. Parse it into the global shader variable.  Later functions
 * will optimize it.
 * @param[in,out] _text
 * @return
 */
qboolean ParseShaderR1(char *_text)
{
	char **text = &_text;
	char *token;
	int  s = 0;

	shader.explicitlyDefined = qtrue;

	token = COM_ParseExt2(text, qtrue);

	if (token[0] != '{')
	{
		Ren_Warning("WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name);
		return qfalse;
	}

	while (1)
	{
		token = COM_ParseExt2(text, qtrue);
		if (!token[0])
		{
			Ren_Warning("WARNING: no concluding '}' in shader %s\n", shader.name);
			return qfalse;
		}

		// end of shader definition
		if (token[0] == '}')
		{
			break;
		}
		// stage definition
		else if (token[0] == '{')
		{
			if (s >= MAX_SHADER_STAGES)
			{
				Ren_Warning("WARNING: too many stages in shader %s (max is %i)\n", shader.name, MAX_SHADER_STAGES);
				return qfalse;
			}

			if (!ParseStage(&stages[s], text))
			{
				Ren_Warning("WARNING: can't parse stages of shader %s @[%.50s ...]\n", shader.name, _text);
				return qfalse;
			}
			stages[s].active = qtrue;
			s++;
			continue;
		}
		// skip stuff that only the QuakeEdRadient needs
		else if (!Q_stricmpn(token, "qer", 3))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip description
		else if (!Q_stricmp(token, "description"))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip renderbump
		else if (!Q_stricmp(token, "renderbump"))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip unsmoothedTangents
		else if (!Q_stricmp(token, "unsmoothedTangents"))
		{
			Ren_Warning("WARNING: unsmoothedTangents keyword not supported in shader '%s'\n", shader.name);
			continue;
		}
		// skip guiSurf
		else if (!Q_stricmp(token, "guiSurf"))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip decalInfo
		else if (!Q_stricmp(token, "decalInfo"))
		{
			Ren_Warning("WARNING: decalInfo keyword not supported in shader '%s'\n", shader.name);
			SkipRestOfLine(text);
			continue;
		}
		// skip Quake4's extra material types
		else if (!Q_stricmp(token, "materialType"))
		{
			Ren_Warning("WARNING: materialType keyword not supported in shader '%s'\n", shader.name);
			SkipRestOfLine(text);
			continue;
		}
		// skip Prey's extra material types
		else if (!Q_stricmpn(token, "matter", 6))
		{
			//Ren_Warning( "WARNING: materialType keyword not supported in shader '%s'\n", shader.name);
			SkipRestOfLine(text);
			continue;
		}
		// sun parms
		else if (!Q_stricmp(token, "xmap_sun") || !Q_stricmp(token, "q3map_sun"))
		{
			float a, b;

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			tr.sunLight[0] = atof(token);

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			tr.sunLight[1] = atof(token);


			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			tr.sunLight[2] = atof(token);

			VectorNormalize(tr.sunLight);

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			a = atof(token);
			VectorScale(tr.sunLight, a, tr.sunLight);

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			a = atof(token);
			a = a / 180 * M_PI;

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name);
				continue;
			}
			b = atof(token);
			b = b / 180 * M_PI;

			tr.sunDirection[0] = cos(a) * cos(b);
			tr.sunDirection[1] = sin(a) * cos(b);
			tr.sunDirection[2] = sin(b);
			continue;
		}
		// noShadows
		else if (!Q_stricmp(token, "noShadows"))
		{
			shader.noShadows = qtrue;
			continue;
		}
		// noSelfShadow
		else if (!Q_stricmp(token, "noSelfShadow"))
		{
			Ren_Warning("WARNING: noSelfShadow keyword not supported in shader '%s'\n", shader.name);
			continue;
		}
		// forceShadows
		else if (!Q_stricmp(token, "forceShadows"))
		{
			Ren_Warning("WARNING: forceShadows keyword not supported in shader '%s'\n", shader.name);
			continue;
		}
		// forceOverlays
		else if (!Q_stricmp(token, "forceOverlays"))
		{
			Ren_Warning("WARNING: forceOverlays keyword not supported in shader '%s'\n", shader.name);
			continue;
		}
		// noPortalFog
		else if (!Q_stricmp(token, "noPortalFog"))
		{
			Ren_Warning("WARNING: noPortalFog keyword not supported in shader '%s'\n", shader.name);
			continue;
		}
		// fogLight
		else if (!Q_stricmp(token, "fogLight"))
		{
			Ren_Warning("WARNING: fogLight keyword not supported in shader '%s'\n", shader.name);
			shader.fogLight = qtrue;
			continue;
		}
		// blendLight
		else if (!Q_stricmp(token, "blendLight"))
		{
			Ren_Warning("WARNING: blendLight keyword not supported in shader '%s'\n", shader.name);
			shader.blendLight = qtrue;
			continue;
		}
		// ambientLight
		else if (!Q_stricmp(token, "ambientLight"))
		{
			Ren_Warning("WARNING: ambientLight keyword not supported in shader '%s'\n", shader.name);
			shader.ambientLight = qtrue;
			continue;
		}
		// volumetricLight
		else if (!Q_stricmp(token, "volumetricLight"))
		{
			shader.volumetricLight = qtrue;
			continue;
		}
		// translucent
		else if (!Q_stricmp(token, "translucent"))
		{
			shader.translucent = qtrue;
			continue;
		}
		// forceOpaque
		else if (!Q_stricmp(token, "forceOpaque"))
		{
			shader.forceOpaque = qtrue;
			continue;
		}
		// forceSolid
		else if (!Q_stricmp(token, "forceSolid") || !Q_stricmp(token, "solid"))
		{
			continue;
		}
		else if (!Q_stricmp(token, "deformVertexes") || !Q_stricmp(token, "deform"))
		{
			ParseDeform(text);
			continue;
		}
		else if (!Q_stricmp(token, "tesssize"))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip noFragment
		if (!Q_stricmp(token, "noFragment"))
		{
			continue;
		}
		// skip stuff that only the xmap needs
		else if (!Q_stricmpn(token, "xmap", 4) || !Q_stricmpn(token, "q3map", 5))
		{
			SkipRestOfLine(text);
			continue;
		}
		// skip stuff that only xmap or the server needs
		else if (!Q_stricmp(token, "surfaceParm"))
		{
			ParseSurfaceParm(text);
			continue;
		}
		// no mip maps
		else if (!Q_stricmp(token, "nomipmap") || !Q_stricmp(token, "nomipmaps"))
		{
			shader.filterType = FT_LINEAR;
			shader.noPicMip   = qtrue;
			continue;
		}
		// no picmip adjustment
		else if (!Q_stricmp(token, "nopicmip"))
		{
			shader.noPicMip = qtrue;
			continue;
		}
		// RF, allow each shader to permit compression if available
		else if (!Q_stricmp(token, "allowcompress"))
		{
			shader.uncompressed = qfalse;
			continue;
		}
		else if (!Q_stricmp(token, "nocompress"))
		{
			shader.uncompressed = qtrue;
			continue;
		}
		// polygonOffset
		else if (!Q_stricmp(token, "polygonOffset"))
		{
			shader.polygonOffset = qtrue;
			continue;
		}
		// parallax mapping
		else if (!Q_stricmp(token, "parallax"))
		{
			shader.parallax = qtrue;
			continue;
		}
		// entityMergable, allowing sprite surfaces from multiple entities
		// to be merged into one batch.  This is a savings for smoke
		// puffs and blood, but can't be used for anything where the
		// shader calcs (not the surface function) reference the entity color or scroll
		else if (!Q_stricmp(token, "entityMergable"))
		{
			shader.entityMergable = qtrue;
			continue;
		}
		// fogParms
		else if (!Q_stricmp(token, "fogParms"))
		{
			if (!ParseVector(text, 3, shader.fogParms.color))
			{
				return qfalse;
			}

			//shader.fogParms.colorInt = ColorBytes4(shader.fogParms.color[0] * tr.identityLight,
			//                                       shader.fogParms.color[1] * tr.identityLight,
			//                                       shader.fogParms.color[2] * tr.identityLight, 1.0);

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: 'fogParms' incomplete - missing opacity value in shader '%s' set to 1\n", shader.name);
				shader.fogParms.depthForOpaque = 1;
			}
			else
			{
				shader.fogParms.depthForOpaque = atof(token);
				shader.fogParms.depthForOpaque = shader.fogParms.depthForOpaque < 1 ? 1 : shader.fogParms.depthForOpaque;
			}
			//shader.fogParms.tcScale = 1.0f / shader.fogParms.depthForOpaque;

			shader.fogVolume = qtrue;
			shader.sort      = SS_FOG;

			// skip any old gradient directions
			SkipRestOfLine(text);
			continue;
		}
		// noFog
		else if (!Q_stricmp(token, "noFog"))
		{
			shader.noFog = qtrue;
			continue;
		}
		// portal
		else if (!Q_stricmp(token, "portal"))
		{
			shader.sort     = SS_PORTAL;
			shader.isPortal = qtrue;

			token = COM_ParseExt2(text, qfalse);
			if (token[0])
			{
				shader.portalRange = atof(token);
			}
			else
			{
				shader.portalRange = 256;
			}
			continue;
		}
		// portal or mirror
		else if (!Q_stricmp(token, "mirror"))
		{
			shader.sort     = SS_PORTAL;
			shader.isPortal = qtrue;
			continue;
		}
		// skyparms <cloudheight> <outerbox> <innerbox>
		else if (!Q_stricmp(token, "skyparms"))
		{
			ParseSkyParms(text);
			continue;
		}
		// This is fixed fog for the skybox/clouds determined solely by the shader
		// it will not change in a level and will not be necessary
		// to force clients to use a sky fog the server says to.
		// skyfogvars <(r,g,b)> <dist>
		else if (!Q_stricmp(token, "skyfogvars"))
		{
			vec3_t fogColor;

			if (!ParseVector(text, 3, fogColor))
			{
				return qfalse;
			}
			token = COM_ParseExt(text, qfalse);

			if (!token[0])
			{
				Ren_Warning("WARNING: missing density value for sky fog\n");
				continue;
			}

			if (atof(token) > 1)
			{
				Ren_Warning("WARNING: last value for skyfogvars is 'density' which needs to be 0.0-1.0\n");
				continue;
			}

			RE_SetFog(FOG_SKY, 0, 5, fogColor[0], fogColor[1], fogColor[2], atof(token));

			continue;
		}
		// ET waterfogvars
		else if (!Q_stricmp(token, "waterfogvars"))
		{
			vec3_t watercolor;
			float  fogvar;

			if (!ParseVector(text, 3, watercolor))
			{
				return qfalse;
			}
			token = COM_ParseExt(text, qfalse);

			if (!token[0])
			{
				Ren_Warning("WARNING: missing density/distance value for water fog\n");
				continue;
			}

			fogvar = atof(token);

			// right now allow one water color per map.  I'm sure this will need
			//          to change at some point, but I'm not sure how to track fog parameters
			//          on a "per-water volume" basis yet.
			if (fogvar == 0)
			{                   // '0' specifies "use the map values for everything except the fog color
				// TODO
			}
			else if (fogvar > 1)
			{                   // distance "linear" fog
				RE_SetFog(FOG_WATER, 0, fogvar, watercolor[0], watercolor[1], watercolor[2], 1.1);
			}
			else
			{                   // density "exp" fog
				RE_SetFog(FOG_WATER, 0, 5, watercolor[0], watercolor[1], watercolor[2], fogvar);
			}
			continue;
		}
		// ET fogvars
		else if (!Q_stricmp(token, "fogvars"))
		{
			vec3_t fogColor;
			float  fogDensity;
			int    fogFar;

			if (!ParseVector(text, 3, fogColor))
			{
				return qfalse;
			}

			token = COM_ParseExt(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing density value for the fog\n");
				continue;
			}

			// NOTE:   fogFar > 1 means the shader is setting the farclip, < 1 means setting
			//         density (so old maps or maps that just need softening fog don't have to care about farclip)

			fogDensity = atof(token);
			if (fogDensity > 1)
			{                   // linear
				fogFar = fogDensity;
			}
			else
			{
				fogFar = 5;
			}

			RE_SetFog(FOG_MAP, 0, fogFar, fogColor[0], fogColor[1], fogColor[2], fogDensity);
			RE_SetFog(FOG_CMD_SWITCHFOG, FOG_MAP, 50, 0, 0, 0, 0);
			continue;
		}
		// ET sunshader <name>
		else if (!Q_stricmp(token, "sunshader"))
		{
			size_t tokenLen;

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing shader name for 'sunshader'\n");
				continue;
			}

			// Don't call tr.sunShader = R_FindShader(token, SHADER_3D_STATIC, qtrue);
			// because it breaks the computation of the current shader
			tokenLen         = strlen(token) + 1;
			tr.sunShaderName = (char *)ri.Hunk_Alloc(sizeof(char) * tokenLen, h_low);
			Q_strncpyz(tr.sunShaderName, token, tokenLen);
		}
		else if (!Q_stricmp(token, "lightgridmulamb"))
		{
			// ambient multiplier for lightgrid
			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing value for 'lightgrid ambient multiplier'\n");
				continue;
			}
			if (atof(token) > 0)
			{
				tr.lightGridMulAmbient = atof(token);
			}
		}
		else if (!Q_stricmp(token, "lightgridmuldir"))
		{
			// directional multiplier for lightgrid
			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing value for 'lightgrid directional multiplier'\n");
				continue;
			}
			if (atof(token) > 0)
			{
				tr.lightGridMulDirected = atof(token);
			}
		}
		// light <value> determines flaring in xmap, not needed here
		else if (!Q_stricmp(token, "light"))
		{
			(void) COM_ParseExt2(text, qfalse);
			continue;
		}
		// cull <face>
		else if (!Q_stricmp(token, "cull"))
		{
			token = COM_ParseExt2(text, qfalse);
			if (token[0] == 0)
			{
				Ren_Warning("WARNING: missing cull parms in shader '%s'\n", shader.name);
				continue;
			}

			if (!Q_stricmp(token, "none") || !Q_stricmp(token, "twoSided") || !Q_stricmp(token, "disable"))
			{
				shader.cullType = CT_TWO_SIDED;
			}
			else if (!Q_stricmp(token, "back") || !Q_stricmp(token, "backside") || !Q_stricmp(token, "backsided"))
			{
				shader.cullType = CT_BACK_SIDED;
			}
			else if (!Q_stricmp(token, "front"))
			{
				// CT_FRONT_SIDED is set per default see R_FindShader - nothing to do just don't throw a warning
			}
			else
			{
				Ren_Warning("WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name);
			}
			continue;
		}
		// distancecull <opaque distance> <transparent distance> <alpha threshold>
		else if (!Q_stricmp(token, "distancecull"))
		{
			int i;

			for (i = 0; i < 3; i++)
			{
				token = COM_ParseExt(text, qfalse);
				if (token[0] == 0)
				{
					Ren_Warning("WARNING: missing distancecull parms in shader '%s'\n", shader.name);
				}
				else
				{
					shader.distanceCull[i] = atof(token);
				}
			}

			if (shader.distanceCull[1] - shader.distanceCull[0] > 0)
			{
				// distanceCull[ 3 ] is an optimization
				shader.distanceCull[3] = 1.0f / (shader.distanceCull[1] - shader.distanceCull[0]);
			}
			else
			{
				shader.distanceCull[0] = 0;
				shader.distanceCull[1] = 0;
				shader.distanceCull[2] = 0;
				shader.distanceCull[3] = 0;
			}
			continue;
		}
		// twoSided
		else if (!Q_stricmp(token, "twoSided"))
		{
			shader.cullType = CT_TWO_SIDED;
			continue;
		}
		// backSided
		else if (!Q_stricmp(token, "backSided"))
		{
			shader.cullType = CT_BACK_SIDED;
			continue;
		}
		// clamp
		else if (!Q_stricmp(token, "clamp"))
		{
			shader.wrapType = WT_CLAMP;
			continue;
		}
		// edgeClamp
		else if (!Q_stricmp(token, "edgeClamp"))
		{
			shader.wrapType = WT_EDGE_CLAMP;
			continue;
		}
		// zeroClamp
		else if (!Q_stricmp(token, "zeroclamp"))
		{
			shader.wrapType = WT_ZERO_CLAMP;
			continue;
		}
		// alphaZeroClamp
		else if (!Q_stricmp(token, "alphaZeroClamp"))
		{
			shader.wrapType = WT_ALPHA_ZERO_CLAMP;
			continue;
		}
		// sort
		else if (!Q_stricmp(token, "sort"))
		{
			ParseSort(text);
			continue;
		}
		// implicit default mapping to eliminate redundant/incorrect explicit shader stages
		else if (!Q_stricmpn(token, "implicit", 8))
		{
			//Ren_Warning( "WARNING: keyword '%s' not supported in shader '%s'\n", token, shader.name);
			//SkipRestOfLine(text);

			// set implicit mapping state
			if (!Q_stricmp(token, "implicitBlend"))
			{
				implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
				implicitCullType  = CT_TWO_SIDED;
			}
			else if (!Q_stricmp(token, "implicitMask"))
			{
				implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_ATEST_GE_128;
				implicitCullType  = CT_TWO_SIDED;
			}
			else                // "implicitMap"
			{
				implicitStateBits = GLS_DEPTHMASK_TRUE;
				implicitCullType  = CT_FRONT_SIDED;
			}

			// get image
			token = COM_ParseExt(text, qfalse);
			if (token[0] != '\0')
			{
				Q_strncpyz(implicitMap, token, sizeof(implicitMap));
			}
			else
			{
				implicitMap[0] = '-';
				implicitMap[1] = '\0';
			}

			continue;
		}
		// spectrum
		else if (!Q_stricmp(token, "spectrum"))
		{
			Ren_Warning("WARNING: spectrum keyword not supported in shader '%s'\n", shader.name);

			token = COM_ParseExt2(text, qfalse);
			if (!token[0])
			{
				Ren_Warning("WARNING: missing parm for 'spectrum' keyword in shader '%s'\n", shader.name);
				continue;
			}
			shader.spectrum      = qtrue;
			shader.spectrumValue = atoi(token);
			continue;
		}
		// diffuseMap <image>
		else if (!Q_stricmp(token, "diffuseMap"))
		{
			ParseDiffuseMap(&stages[s], text);
			s++;
			continue;
		}
		// normalMap <image>
		else if (!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap"))
		{
			ParseNormalMap(&stages[s], text);
			s++;
			continue;
		}
		// specularMap <image>
		else if (!Q_stricmp(token, "specularMap"))
		{
			ParseSpecularMap(&stages[s], text);
			s++;
			continue;
		}
		// glowMap <image>
		else if (!Q_stricmp(token, "glowMap"))
		{
			ParseGlowMap(&stages[s], text);
			s++;
			continue;
		}
		// reflectionMap <image>
		else if (!Q_stricmp(token, "reflectionMap"))
		{
			ParseReflectionMap(&stages[s], text);
			s++;
			continue;
		}
		// reflectionMapBlended <image>
		else if (!Q_stricmp(token, "reflectionMapBlended"))
		{
			ParseReflectionMapBlended(&stages[s], text);
			s++;
			continue;
		}
		// lightMap <image>
		else if (!Q_stricmp(token, "lightMap"))
		{
			Ren_Warning("WARNING: obsolete lightMap keyword not supported in shader '%s'\n", shader.name);
			SkipRestOfLine(text);
			continue;
		}
		// lightFalloffImage <image>
		else if (!Q_stricmp(token, "lightFalloffImage"))
		{
			ParseLightFalloffImage(&stages[s], text);
			s++;
			continue;
		}
		// Doom 3 DECAL_MACRO
		else if (!Q_stricmp(token, "DECAL_MACRO"))
		{
			shader.polygonOffset      = qtrue;
			shader.sort               = SS_DECAL;
			SurfaceParm("discrete");
			SurfaceParm("noShadows");
			continue;
		}
		// Prey DECAL_ALPHATEST_MACRO
		else if (!Q_stricmp(token, "DECAL_ALPHATEST_MACRO"))
		{
			// what's different?
			shader.polygonOffset      = qtrue;
			shader.sort               = SS_DECAL;
			SurfaceParm("discrete");
			SurfaceParm("noShadows");
			continue;
		}
		else if (SurfaceParm(token))
		{
			continue;
		}
		else
		{
			Ren_Warning("WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name);
			SkipRestOfLine(text);
			continue;
		}
	}

	// ignore shaders that don't have any stages, unless it is a sky or fog
	if (s == 0 && !shader.forceOpaque && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG) && implicitMap[0] == '\0')
	{
		return qfalse;
	}

	return qtrue;
}