/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 

This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/




/*
===============================================================================

	idDeclAF

===============================================================================
*/

/*
================
idAFVector::idAFVector
================
*/
idAFVector::idAFVector( void ) {
	type = VEC_COORDS;
	vec.Zero();
	negate = false;
}

/*
================
idAFVector::Parse
================
*/
bool idAFVector::Parse( idLexer &src ) {
	idToken token;

	if ( !src.ReadToken( &token ) ) {
		return false;
	}

	if ( token == "-" ) {
		negate = true;
		if ( !src.ReadToken( &token ) ) {
			return false;
		}
	}
	else {
		negate = false;
	}

	if ( token == "(" ) {
		type = idAFVector::VEC_COORDS;
		vec.x = src.ParseFloat();
		src.ExpectTokenString( "," );
		vec.y = src.ParseFloat();
		src.ExpectTokenString( "," );
		vec.z = src.ParseFloat();
		src.ExpectTokenString( ")" );
	}
	else if ( token == "joint" ) {
		type = idAFVector::VEC_JOINT;
		src.ExpectTokenString( "(" );
		src.ReadToken( &token );
		joint1 = token;
		src.ExpectTokenString( ")" );
	}
	else if ( token == "bonecenter" ) {
		type = idAFVector::VEC_BONECENTER;
		src.ExpectTokenString( "(" );
		src.ReadToken( &token );
		joint1 = token;
		src.ExpectTokenString( "," );
		src.ReadToken( &token );
		joint2 = token;
		src.ExpectTokenString( ")" );
	}
	else if ( token == "bonedir" ) {
		type = idAFVector::VEC_BONEDIR;
		src.ExpectTokenString( "(" );
		src.ReadToken( &token );
		joint1 = token;
		src.ExpectTokenString( "," );
		src.ReadToken( &token );
		joint2 = token;
		src.ExpectTokenString( ")" );
	}
	else {
		src.Error( "unknown token %s in vector", token.c_str() );
		return false;
	}

	return true;
}

/*
================
idAFVector::Finish
================
*/
bool idAFVector::Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const {
	idMat3 axis;
	idVec3 start, end;

	switch( type ) {
		case idAFVector::VEC_COORDS: {
			break;
		}
		case idAFVector::VEC_JOINT: {
			if ( !GetJointTransform( model, frame, joint1, vec, axis ) ) {
				common->Warning( "invalid joint %s in joint() in '%s'", joint1.c_str(), fileName );
				vec.Zero();
			}
			break;
		}
		case idAFVector::VEC_BONECENTER: {
			if ( !GetJointTransform( model, frame, joint1, start, axis ) ) {
				common->Warning( "invalid joint %s in bonecenter() in '%s'", joint1.c_str(), fileName );
				start.Zero();
			}
			if ( !GetJointTransform( model, frame, joint2, end, axis ) ) {
				common->Warning( "invalid joint %s in bonecenter() in '%s'", joint2.c_str(), fileName );
				end.Zero();
			}
			vec = ( start + end ) * 0.5f;
			break;
		}
		case idAFVector::VEC_BONEDIR: {
			if ( !GetJointTransform( model, frame, joint1, start, axis ) ) {
				common->Warning( "invalid joint %s in bonedir() in '%s'", joint1.c_str(), fileName );
				start.Zero();
			}
			if ( !GetJointTransform( model, frame, joint2, end, axis ) ) {
				common->Warning( "invalid joint %s in bonedir() in '%s'", joint2.c_str(), fileName );
				end.Zero();
			}
			vec = ( end - start );
			break;
		}
		default: {
			vec.Zero();
			break;
		}
	}

	if ( negate ) {
		vec = -vec;
	}

	return true;
}

/*
================
idAFVector::Write
================
*/
bool idAFVector::Write( idFile *f ) const {

	if ( negate ) {
		f->WriteFloatString( "-" );
	}
	switch( type ) {
		case idAFVector::VEC_COORDS: {
			f->WriteFloatString( "( %f, %f, %f )", vec.x, vec.y, vec.z );
			break;
		}
		case idAFVector::VEC_JOINT: {
			f->WriteFloatString( "joint( \"%s\" )", joint1.c_str() );
			break;
		}
		case idAFVector::VEC_BONECENTER: {
			f->WriteFloatString( "bonecenter( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() );
			break;
		}
		case idAFVector::VEC_BONEDIR: {
			f->WriteFloatString( "bonedir( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() );
			break;
		}
		default: {
			break;
		}
	}
	return true;
}

/*
================
idAFVector::ToString
================
*/
const char *idAFVector::ToString( idStr &str, const int precision ) {

	switch( type ) {
		case idAFVector::VEC_COORDS: {
			char format[128];
			char buffer[128];
			const int clampedPrecision = idMath::ClampInt( 0, 9, precision );
			idStr::snPrintf( format, sizeof( format ), "( %%.%df, %%.%df, %%.%df )", clampedPrecision, clampedPrecision, clampedPrecision );
			idStr::snPrintf( buffer, sizeof( buffer ), format, vec.x, vec.y, vec.z );
			str = buffer;
			break;
		}
		case idAFVector::VEC_JOINT: {
			str = "joint( \"";
			str += joint1;
			str += "\" )";
			break;
		}
		case idAFVector::VEC_BONECENTER: {
			str = "bonecenter( \"";
			str += joint1;
			str += "\", \"";
			str += joint2;
			str += "\" )";
			break;
		}
		case idAFVector::VEC_BONEDIR: {
			str = "bonedir( \"";
			str += joint1;
			str += "\", \"";
			str += joint2;
			str += "\" )";
			break;
		}
		default: {
			break;
		}
	}
	if ( negate ) {
		str = "-" + str;
	}
	return str.c_str();
}

/*
================
idDeclAF_Body::SetDefault
================
*/
void idDeclAF_Body::SetDefault( const idDeclAF *file ) {
	name = "noname";
	modelType = TRM_BOX;
	v1.type = idAFVector::VEC_COORDS;
	v1.ToVec3().x = v1.ToVec3().y = v1.ToVec3().z = -10.0f;
	v2.type = idAFVector::VEC_COORDS;
	v2.ToVec3().x = v2.ToVec3().y = v2.ToVec3().z = 10.0f;
	numSides = 3;
	origin.ToVec3().Zero();
	angles.Zero();
	density = 0.2f;
	inertiaScale.Identity();
	linearFriction = file->defaultLinearFriction;
	angularFriction = file->defaultAngularFriction;
	contactFriction = file->defaultContactFriction;
	contents = file->contents;
	clipMask = file->clipMask;
	selfCollision = file->selfCollision;
	frictionDirection.ToVec3().Zero();
	contactMotorDirection.ToVec3().Zero();
	jointName = "origin";
	jointMod = DECLAF_JOINTMOD_AXIS;
	containedJoints = "*origin";
}

/*
================
idDeclAF_Constraint::SetDefault
================
*/
void idDeclAF_Constraint::SetDefault( const idDeclAF *file ) {
	name = "noname";
	type = DECLAF_CONSTRAINT_UNIVERSALJOINT;
	if ( file->bodies.Num() ) {
		body1 = file->bodies[0]->name;
	}
	else {
		body1 = "world";
	}
	body2 = "world";
	friction = file->defaultConstraintFriction;
	anchor.ToVec3().Zero();
	anchor2.ToVec3().Zero();
	axis.ToVec3().Set( 1.0f, 0.0f, 0.0f );
	shaft[0].ToVec3().Set( 0.0f, 0.0f, -1.0f );
	shaft[1].ToVec3().Set( 0.0f, 0.0f, 1.0f );
	limit = idDeclAF_Constraint::LIMIT_NONE;
	limitAngles[0] =
	limitAngles[1] =
	limitAngles[2] = 0.0f;
	limitAxis.ToVec3().Set( 0.0f, 0.0f, -1.0f );
}

/*
================
idDeclAF::WriteBody
================
*/
bool idDeclAF::WriteBody( idFile *f, const idDeclAF_Body &body ) const {
	idStr str;

	f->WriteFloatString( "\nbody \"%s\" {\n", body.name.c_str() );
	f->WriteFloatString( "\tjoint \"%s\"\n", body.jointName.c_str() );
	f->WriteFloatString( "\tmod %s\n", JointModToString( body.jointMod ) );
	switch( body.modelType ) {
		case TRM_BOX: {
	        f->WriteFloatString( "\tmodel box( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( " )\n" );
			break;
		}
		case TRM_OCTAHEDRON: {
	        f->WriteFloatString( "\tmodel octahedron( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( " )\n" );
			break;
		}
		case TRM_DODECAHEDRON: {
	        f->WriteFloatString( "\tmodel dodecahedron( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( " )\n" );
			break;
		}
		case TRM_CYLINDER: {
	        f->WriteFloatString( "\tmodel cylinder( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( ", %d )\n", body.numSides );
			break;
		}	
		case TRM_CONE: {
	        f->WriteFloatString( "\tmodel cone( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( ", %d )\n", body.numSides );
			break;
		}
		case TRM_BONE: {
	        f->WriteFloatString( "\tmodel bone( " );
			body.v1.Write( f );
			f->WriteFloatString( ", " );
			body.v2.Write( f );
			f->WriteFloatString( ", %f )\n", body.width );
			break;
		}
		default:
			assert( 0 );
			break;
	}
	f->WriteFloatString( "\torigin " );
	body.origin.Write( f );
	f->WriteFloatString( "\n" );
	if ( body.angles != ang_zero ) {
		f->WriteFloatString( "\tangles ( %f, %f, %f )\n", body.angles.pitch, body.angles.yaw, body.angles.roll );
	}
	f->WriteFloatString( "\tdensity %f\n", body.density );
	if ( body.inertiaScale != mat3_identity ) {
		const idMat3 &ic = body.inertiaScale;
		f->WriteFloatString( "\tinertiaScale (%f %f %f %f %f %f %f %f %f)\n",
												ic[0][0], ic[0][1], ic[0][2],
												ic[1][0], ic[1][1], ic[1][2],
												ic[2][0], ic[2][1], ic[2][2] );
	}
	if ( body.linearFriction != -1 ) {
		f->WriteFloatString( "\tfriction %f, %f, %f\n", body.linearFriction, body.angularFriction, body.contactFriction );
	}
	f->WriteFloatString( "\tcontents %s\n", ContentsToString( body.contents, str ) );
	f->WriteFloatString( "\tclipMask %s\n", ContentsToString( body.clipMask, str ) );
	f->WriteFloatString( "\tselfCollision %d\n", body.selfCollision );
	if ( body.frictionDirection.ToVec3() != vec3_origin ) {
		f->WriteFloatString( "\tfrictionDirection " );
		body.frictionDirection.Write( f );
		f->WriteFloatString( "\n" );
	}
	if ( body.contactMotorDirection.ToVec3() != vec3_origin ) {
		f->WriteFloatString( "\tcontactMotorDirection " );
		body.contactMotorDirection.Write( f );
		f->WriteFloatString( "\n" );
	}
	f->WriteFloatString( "\tcontainedJoints \"%s\"\n", body.containedJoints.c_str() );
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteFixed
================
*/
bool idDeclAF::WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nfixed \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteBallAndSocketJoint
================
*/
bool idDeclAF::WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nballAndSocketJoint \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "\tanchor " );
	c.anchor.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tfriction %f\n", c.friction );
	if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) {
		f->WriteFloatString( "\tconeLimit " );
		c.limitAxis.Write( f );
		f->WriteFloatString( ", %f, ", c.limitAngles[0] );
		c.shaft[0].Write( f );
		f->WriteFloatString( "\n" );
	}
	else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) {
		f->WriteFloatString( "\tpyramidLimit " );
		c.limitAxis.Write( f );
		f->WriteFloatString( ", %f, %f, %f, ", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] );
		c.shaft[0].Write( f );
		f->WriteFloatString( "\n" );
	}
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteUniversalJoint
================
*/
bool idDeclAF::WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nuniversalJoint \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "\tanchor " );
	c.anchor.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tshafts " );
	c.shaft[0].Write( f );
	f->WriteFloatString( ", " );
	c.shaft[1].Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tfriction %f\n", c.friction );
	if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) {
		f->WriteFloatString( "\tconeLimit " );
		c.limitAxis.Write( f );
		f->WriteFloatString( ", %f\n", c.limitAngles[0] );
	}
	else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) {
		f->WriteFloatString( "\tpyramidLimit " );
		c.limitAxis.Write( f );
		f->WriteFloatString( ", %f, %f, %f\n", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] );
	}
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteHinge
================
*/
bool idDeclAF::WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nhinge \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "\tanchor " );
	c.anchor.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\taxis " );
	c.axis.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tfriction %f\n", c.friction );
	if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) {
		f->WriteFloatString( "\tlimit " );
		f->WriteFloatString( "%f, %f, %f", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] );
		f->WriteFloatString( "\n" );
	}
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteSlider
================
*/
bool idDeclAF::WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nslider \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "\taxis " );
	c.axis.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tfriction %f\n", c.friction );
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteSpring
================
*/
bool idDeclAF::WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const {
	f->WriteFloatString( "\nspring \"%s\" {\n", c.name.c_str() );
	f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() );
	f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() );
	f->WriteFloatString( "\tanchor1 " );
	c.anchor.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tanchor2 " );
	c.anchor2.Write( f );
	f->WriteFloatString( "\n" );
	f->WriteFloatString( "\tfriction %f\n", c.friction );
	f->WriteFloatString( "\tstretch %f\n", c.stretch );
	f->WriteFloatString( "\tcompress %f\n", c.compress );
	f->WriteFloatString( "\tdamping %f\n", c.damping );
	f->WriteFloatString( "\trestLength %f\n", c.restLength );
	f->WriteFloatString( "\tminLength %f\n", c.minLength );
	f->WriteFloatString( "\tmaxLength %f\n", c.maxLength );
	f->WriteFloatString( "}\n" );
	return true;
}

/*
================
idDeclAF::WriteConstraint
================
*/
bool idDeclAF::WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const {
	switch( c.type ) {
		case DECLAF_CONSTRAINT_FIXED:
			return WriteFixed( f, c );
		case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT:
			return WriteBallAndSocketJoint( f, c );
		case DECLAF_CONSTRAINT_UNIVERSALJOINT:
			return WriteUniversalJoint( f, c );
		case DECLAF_CONSTRAINT_HINGE:
			return WriteHinge( f, c );
		case DECLAF_CONSTRAINT_SLIDER:
			return WriteSlider( f, c );
		case DECLAF_CONSTRAINT_SPRING:
			return WriteSpring( f, c );
		default:
			break;
	}
	return false;
}

/*
================
idDeclAF::WriteSettings
================
*/
bool idDeclAF::WriteSettings( idFile *f ) const {
	idStr str;

	f->WriteFloatString( "\nsettings {\n" );
	f->WriteFloatString( "\tmodel \"%s\"\n", model.c_str() );
	f->WriteFloatString( "\tskin \"%s\"\n",	skin.c_str() );
	f->WriteFloatString( "\tfriction %f, %f, %f, %f\n", defaultLinearFriction, defaultAngularFriction, defaultContactFriction, defaultConstraintFriction );
	f->WriteFloatString( "\tsuspendSpeed %f, %f, %f, %f\n", suspendVelocity[0], suspendVelocity[1], suspendAcceleration[0], suspendAcceleration[1] );
	f->WriteFloatString( "\tnoMoveTime %f\n", noMoveTime );
	f->WriteFloatString( "\tnoMoveTranslation %f\n", noMoveTranslation );
	f->WriteFloatString( "\tnoMoveRotation %f\n", noMoveRotation );
	f->WriteFloatString( "\tminMoveTime %f\n", minMoveTime );
	f->WriteFloatString( "\tmaxMoveTime %f\n", maxMoveTime );
	f->WriteFloatString( "\ttotalMass %f\n", totalMass );
	f->WriteFloatString( "\tcontents %s\n", ContentsToString( contents, str ) );
	f->WriteFloatString( "\tclipMask %s\n", ContentsToString( clipMask, str ) );
	f->WriteFloatString( "\tselfCollision %d\n", selfCollision );
	if ( fastEval ) {
		f->WriteFloatString( "\tfastEval\n" );
	}
	f->WriteFloatString( "}\n" );
	return true;
}


/*
================
idDeclAF::RebuildTextSource
================
*/
bool idDeclAF::RebuildTextSource( void ) {
	int i;
	idFile_Memory f;

	f.WriteFloatString("\n\n/*\n"
						"\tGenerated by the Articulated Figure Editor.\n"
						"\tDo not edit directly but launch the game and type 'editAFs' on the console.\n"
						"*/\n" );

	f.WriteFloatString( "\narticulatedFigure %s {\n", GetName() );

	if ( !WriteSettings( &f ) ) {
		return false;
	}

	for ( i = 0; i < bodies.Num(); i++ ) {
		if ( !WriteBody( &f, *bodies[i] ) ) {
			return false;
		}
	}

	for ( i = 0; i < constraints.Num(); i++ ) {
		if ( !WriteConstraint( &f, *constraints[i] ) ) {
			return false;
		}
	}

	f.WriteFloatString( "\n}" );

	SetText( f.GetDataPtr() );

	return true;
}

/*
================
idDeclAF::Save
================
*/
bool idDeclAF::Save( void ) {
	RebuildTextSource();
	ReplaceSourceFileText();
	modified = false;
	return true;
}

/*
================
idDeclAF::ContentsFromString
================
*/
int idDeclAF::ContentsFromString( const char *str ) {
	int c;
	idToken token;
	idLexer src( str, idStr::Length( str ), "idDeclAF::ContentsFromString" );

	c = 0;
	while( src.ReadToken( &token ) ) {
		if ( token.Icmp( "none" ) == 0 ) {
			c = 0;
		}
		else if ( token.Icmp( "solid" ) == 0 ) {
			c |= CONTENTS_SOLID;
		}
		else if ( token.Icmp( "body" ) == 0 ) {
			c |= CONTENTS_BODY;
		}
		else if ( token.Icmp( "corpse" ) == 0 ) {
			c |= CONTENTS_CORPSE;
		}
		else if ( token.Icmp( "playerclip" ) == 0 ) {
			c |= CONTENTS_PLAYERCLIP;
		}
		else if ( token.Icmp( "monsterclip" ) == 0 ) {
			c |= CONTENTS_MONSTERCLIP;
		}
		else if ( token.Icmp( "vehicleclip" ) == 0 ) {
			c |= CONTENTS_VEHICLECLIP;
		}
		else if ( token.Icmp( "flyclip" ) == 0 ) {
			c |= CONTENTS_FLYCLIP;
		}
		else if ( token.Icmp( "itemclip" ) == 0 ) {
			c |= CONTENTS_ITEMCLIP;
		}
		else if ( token == "," ) {
			continue;
		}
		else {
			return c;
		}
	}
	return c;
}

/*
================
idDeclAF::ContentsToString
================
*/
const char *idDeclAF::ContentsToString( const int contents, idStr &str ) {
	str = "";
	if ( contents & CONTENTS_SOLID ) {
		if ( str.Length() ) str += ", ";
		str += "solid";
	}
	if ( contents & CONTENTS_BODY ) {
		if ( str.Length() ) str += ", ";
		str += "body";
	}
	if ( contents & CONTENTS_CORPSE ) {
		if ( str.Length() ) str += ", ";
		str += "corpse";
	}
	if ( contents & CONTENTS_PLAYERCLIP ) {
		if ( str.Length() ) str += ", ";
		str += "playerclip";
	}
	if ( contents & CONTENTS_MONSTERCLIP ) {
		if ( str.Length() ) str += ", ";
		str += "monsterclip";
	}
	if ( contents & CONTENTS_VEHICLECLIP ) {
		if ( str.Length() ) str += ", ";
		str += "vehicleclip";
	}
	if ( contents & CONTENTS_FLYCLIP ) {
		if ( str.Length() ) str += ", ";
		str += "flyclip";
	}
	if ( contents & CONTENTS_ITEMCLIP ) {
		if ( str.Length() ) str += ", ";
		str += "itemclip";
	}
	if ( str[0] == '\0' ) {
		str = "none";
	}
	return str.c_str();
}

/*
================
idDeclAF::JointModFromString
================
*/
declAFJointMod_t idDeclAF::JointModFromString( const char *str ) {
	if ( idStr::Icmp( str, "orientation" ) == 0 ) {
		return DECLAF_JOINTMOD_AXIS;
	}
	if ( idStr::Icmp( str, "position" ) == 0 ) {
		return DECLAF_JOINTMOD_ORIGIN;
	}
	if ( idStr::Icmp( str, "both" ) == 0 ) {
		return DECLAF_JOINTMOD_BOTH;
	}
	return DECLAF_JOINTMOD_AXIS;
}

/*
================
idDeclAF::JointModToString
================
*/
const char * idDeclAF::JointModToString( declAFJointMod_t jointMod ) {
	switch( jointMod ) {
		case DECLAF_JOINTMOD_AXIS: {
			return "orientation";
		}
		case DECLAF_JOINTMOD_ORIGIN: {
			return "position";
		}
		case DECLAF_JOINTMOD_BOTH: {
			return "both";
		}
	}
	return "orientation";
}

/*
=================
idDeclAF::Size
=================
*/
size_t idDeclAF::Size( void ) const {
	return sizeof( idDeclAF );
}

/*
================
idDeclAF::ParseContents
================
*/
bool idDeclAF::ParseContents( idLexer &src, int &c ) const {
	idToken token;
	idStr str;

	while( src.ReadToken( &token ) ) {
		str += token;
		if ( !src.CheckTokenString( "," ) ) {
			break;
		}
		str += ",";
	}
	c = ContentsFromString( str );
	return true;
}

/*
================
idDeclAF::ParseBody
================
*/
bool idDeclAF::ParseBody( idLexer &src ) {
	bool hasJoint = false;
	idToken token;
	idAFVector angles;
	idDeclAF_Body *body = new idDeclAF_Body;

	bodies.Alloc() = body;

	body->SetDefault( this );

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	body->name = token;
	if ( !body->name.Icmp( "origin" ) || !body->name.Icmp( "world" ) ) {
		src.Error( "a body may not be named \"origin\" or \"world\"" );
		return false;
	}

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "model" ) ) {
			if ( !src.ExpectTokenType( TT_NAME, 0, &token ) ) {
				return false;
			}
			if ( !token.Icmp( "box" ) ) {
				body->modelType = TRM_BOX;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "octahedron" ) ) {
				body->modelType = TRM_OCTAHEDRON;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "dodecahedron" ) ) {
				body->modelType = TRM_DODECAHEDRON;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "cylinder" ) ) {
				body->modelType = TRM_CYLINDER;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( "," ) ) {
					return false;
				}
				body->numSides = src.ParseInt();
				if ( !src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "cone" ) ) {
				body->modelType = TRM_CONE;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( "," ) ) {
					return false;
				}
				body->numSides = src.ParseInt();
				if ( !src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "bone" ) ) {
				body->modelType = TRM_BONE;
				if ( !src.ExpectTokenString( "(" ) ||
					!body->v1.Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!body->v2.Parse( src ) ||
					!src.ExpectTokenString( "," ) ) {
					return false;
				}
				body->width = src.ParseFloat();
				if ( !src.ExpectTokenString( ")" ) ) {
					return false;
				}
			} else if ( !token.Icmp( "custom" ) ) {
				src.Error( "custom models not yet implemented" );
				return false;
			} else {
				src.Error( "unkown model type %s", token.c_str() );
				return false;
			}
		} else if ( !token.Icmp( "origin" ) ) {
			if ( !body->origin.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "angles" ) ) {
			if ( !angles.Parse( src ) ) {
				return false;
			}
			body->angles = idAngles( angles.ToVec3().x, angles.ToVec3().y, angles.ToVec3().z );
		} else if ( !token.Icmp( "joint" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
			body->jointName = token;
			hasJoint = true;
		} else if ( !token.Icmp( "mod" ) ) {
			if ( !src.ExpectAnyToken( &token ) ) {
				return false;
			}
			body->jointMod = JointModFromString( token.c_str() );
		} else if ( !token.Icmp( "density" ) ) {
			body->density = src.ParseFloat();
		} else if ( !token.Icmp( "inertiaScale" ) ) {
			src.Parse1DMatrix( 9, body->inertiaScale[0].ToFloatPtr() );
		} else if ( !token.Icmp( "friction" ) ) {
			body->linearFriction = src.ParseFloat();
			src.ExpectTokenString( "," );
			body->angularFriction = src.ParseFloat();
			src.ExpectTokenString( "," );
			body->contactFriction = src.ParseFloat();
		} else if ( !token.Icmp( "contents" ) ) {
			ParseContents( src, body->contents );
		} else if ( !token.Icmp( "clipMask" ) ) {
			ParseContents( src, body->clipMask );
		} else if ( !token.Icmp( "selfCollision" ) ) {
			body->selfCollision = src.ParseBool();
		} else if ( !token.Icmp( "containedjoints" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
			body->containedJoints = token;
		} else if ( !token.Icmp( "frictionDirection" ) ) {
			if ( !body->frictionDirection.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "contactMotorDirection" ) ) {
			if ( !body->contactMotorDirection.Parse( src ) ) {
				return false;
			}
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in body", token.c_str() );
			return false;
		}
	}

	if ( body->modelType == TRM_INVALID ) {
		src.Error( "no model set for body" );
		return false;
	}

	if ( !hasJoint ) {
		src.Error( "no joint set for body" );
		return false;
	}

	body->clipMask |= CONTENTS_MOVEABLECLIP;

	return true;
}

/*
================
idDeclAF::ParseFixed
================
*/
bool idDeclAF::ParseFixed( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_FIXED;
	constraint->name = token;

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in ball and socket joint", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseBallAndSocketJoint
================
*/
bool idDeclAF::ParseBallAndSocketJoint( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_BALLANDSOCKETJOINT;
	constraint->limit = idDeclAF_Constraint::LIMIT_NONE;
	constraint->name = token;
	constraint->friction = 0.5f;
	constraint->anchor.ToVec3().Zero();
	constraint->shaft[0].ToVec3().Zero();

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( !token.Icmp( "anchor" ) ) {
			if ( !constraint->anchor.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "conelimit" ) ) {
			if ( !constraint->limitAxis.Parse( src ) ||
				!src.ExpectTokenString( "," ) ) {
					return false;
			}
			constraint->limitAngles[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ||
				!constraint->shaft[0].Parse( src ) ) {
				return false;
			}
			constraint->limit = idDeclAF_Constraint::LIMIT_CONE;
		} else if ( !token.Icmp( "pyramidlimit" ) ) {
			if ( !constraint->limitAxis.Parse( src ) ||
				!src.ExpectTokenString( "," ) ) {
					return false;
			}
			constraint->limitAngles[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[1] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[2] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ||
				!constraint->shaft[0].Parse( src ) ) {
				return false;
			}
			constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID;
		} else if ( !token.Icmp( "friction" ) ) {
			constraint->friction = src.ParseFloat();
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in ball and socket joint", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseUniversalJoint
================
*/
bool idDeclAF::ParseUniversalJoint( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_UNIVERSALJOINT;
	constraint->limit = idDeclAF_Constraint::LIMIT_NONE;
	constraint->name = token;
	constraint->friction = 0.5f;
	constraint->anchor.ToVec3().Zero();
	constraint->shaft[0].ToVec3().Zero();
	constraint->shaft[1].ToVec3().Zero();

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( !token.Icmp( "anchor" ) ) {
			if ( !constraint->anchor.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "shafts" ) ) {
			if ( !constraint->shaft[0].Parse( src ) ||
					!src.ExpectTokenString( "," ) ||
					!constraint->shaft[1].Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "conelimit" ) ) {
			if ( !constraint->limitAxis.Parse( src ) ||
				!src.ExpectTokenString( "," ) ) {
					return false;
			}
			constraint->limitAngles[0] = src.ParseFloat();
			constraint->limit = idDeclAF_Constraint::LIMIT_CONE;
		} else if ( !token.Icmp( "pyramidlimit" ) ) {
			if ( !constraint->limitAxis.Parse( src ) ||
				!src.ExpectTokenString( "," ) ) {
					return false;
			}
			constraint->limitAngles[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[1] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[2] = src.ParseFloat();
			constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID;
		} else if ( !token.Icmp( "friction" ) ) {
			constraint->friction = src.ParseFloat();
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in universal joint", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseHinge
================
*/
bool idDeclAF::ParseHinge( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_HINGE;
	constraint->limit = idDeclAF_Constraint::LIMIT_NONE;
	constraint->name = token;
	constraint->friction = 0.5f;
	constraint->anchor.ToVec3().Zero();
	constraint->axis.ToVec3().Zero();

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( !token.Icmp( "anchor" ) ) {
			if ( !constraint->anchor.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "axis" ) ) {
			if ( !constraint->axis.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "limit" ) ) {
			constraint->limitAngles[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[1] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			constraint->limitAngles[2] = src.ParseFloat();
			constraint->limit = idDeclAF_Constraint::LIMIT_CONE;
		} else if ( !token.Icmp( "friction" ) ) {
			constraint->friction = src.ParseFloat();
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in hinge", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseSlider
================
*/
bool idDeclAF::ParseSlider( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_SLIDER;
	constraint->limit = idDeclAF_Constraint::LIMIT_NONE;
	constraint->name = token;
	constraint->friction = 0.5f;

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( !token.Icmp( "axis" ) ) {
			if ( !constraint->axis.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "friction" ) ) {
			constraint->friction = src.ParseFloat();
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in slider", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseSpring
================
*/
bool idDeclAF::ParseSpring( idLexer &src ) {
	idToken token;
	idDeclAF_Constraint *constraint = new idDeclAF_Constraint;

	constraint->SetDefault( this );
	constraints.Alloc() = constraint;

	if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ||
			!src.ExpectTokenString( "{" ) ) {
		return false;
	}

	constraint->type = DECLAF_CONSTRAINT_SPRING;
	constraint->limit = idDeclAF_Constraint::LIMIT_NONE;
	constraint->name = token;
	constraint->friction = 0.5f;

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "body1" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body1 = token;
		} else if ( !token.Icmp( "body2" ) ) {
			src.ExpectTokenType( TT_STRING, 0, &token );
			constraint->body2 = token;
		} else if ( !token.Icmp( "anchor1" ) ) {
			if ( !constraint->anchor.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "anchor2" ) ) {
			if ( !constraint->anchor2.Parse( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "friction" ) ) {
			constraint->friction = src.ParseFloat();
		} else if ( !token.Icmp( "stretch" ) ) {
			constraint->stretch = src.ParseFloat();
		} else if ( !token.Icmp( "compress" ) ) {
			constraint->compress = src.ParseFloat();
		} else if ( !token.Icmp( "damping" ) ) {
			constraint->damping = src.ParseFloat();
		} else if ( !token.Icmp( "restLength" ) ) {
			constraint->restLength = src.ParseFloat();
		} else if ( !token.Icmp( "minLength" ) ) {
			constraint->minLength = src.ParseFloat();
		} else if ( !token.Icmp( "maxLength" ) ) {
			constraint->maxLength = src.ParseFloat();
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in spring", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::ParseSettings
================
*/
bool idDeclAF::ParseSettings( idLexer &src ) {
	idToken token;

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

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "mesh" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
		} else if ( !token.Icmp( "anim" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
		} else if ( !token.Icmp( "model" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
			model = token;
		} else if ( !token.Icmp( "skin" ) ) {
			if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) {
				return false;
			}
			skin = token;
		} else if ( !token.Icmp( "friction" ) ) {

			defaultLinearFriction = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			defaultAngularFriction = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			defaultContactFriction = src.ParseFloat();
			if ( src.CheckTokenString( "," ) ) {
				defaultConstraintFriction = src.ParseFloat();
			}
		} else if ( !token.Icmp( "totalMass" ) ) {
			totalMass = src.ParseFloat();
		} else if ( !token.Icmp( "suspendSpeed" ) ) {

			suspendVelocity[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			suspendVelocity[1] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			suspendAcceleration[0] = src.ParseFloat();
			if ( !src.ExpectTokenString( "," ) ) {
				return false;
			}
			suspendAcceleration[1] = src.ParseFloat();
		} else if ( !token.Icmp( "noMoveTime" ) ) {
			noMoveTime = src.ParseFloat();
		} else if ( !token.Icmp( "noMoveTranslation" ) ) {
			noMoveTranslation = src.ParseFloat();
		} else if ( !token.Icmp( "noMoveRotation" ) ) {
			noMoveRotation = src.ParseFloat();
		} else if ( !token.Icmp( "minMoveTime" ) ) {
			minMoveTime = src.ParseFloat();
		} else if ( !token.Icmp( "maxMoveTime" ) ) {
			maxMoveTime = src.ParseFloat();
		} else if ( !token.Icmp( "contents" ) ) {
			ParseContents( src, contents );
		} else if ( !token.Icmp( "clipMask" ) ) {
			ParseContents( src, clipMask );
		} else if ( !token.Icmp( "selfCollision" ) ) {
			selfCollision = src.ParseBool();
		} else if ( !token.Icmp( "fastEval" ) ) {
			idToken valueToken;
			if ( !src.ReadToken( &valueToken ) ) {
				return false;
			}

			// Retail treats bare "fastEval" as true. Keep that behavior, while
			// also accepting explicit boolean values for openQ4 tooling.
			if ( !valueToken.Icmp( "1" ) || !valueToken.Icmp( "true" ) ) {
				fastEval = true;
			} else if ( !valueToken.Icmp( "0" ) || !valueToken.Icmp( "false" ) ) {
				fastEval = false;
			} else {
				fastEval = true;
				src.UnreadToken( &valueToken );
			}
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown token %s in settings", token.c_str() );
			return false;
		}
	}

	return true;
}

/*
================
idDeclAF::Parse
================
*/
bool idDeclAF::Parse( const char *text, const int textLength ) {
	return Parse( text, textLength, false );
}

/*
================
idDeclAF::Parse
================
*/
bool idDeclAF::Parse( const char *text, const int textLength, bool noCaching ) {
	int i, j;
	idLexer src;
	idToken token;

	src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
	src.SetFlags( DECL_LEXER_FLAGS );
	src.SkipUntilString( "{" );

	while( src.ReadToken( &token ) ) {

		if ( !token.Icmp( "settings" ) ) {
			if ( !ParseSettings( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "body" ) ) {
			if ( !ParseBody( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "fixed" ) ) {
			if ( !ParseFixed( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "ballAndSocketJoint" ) ) {
			if ( !ParseBallAndSocketJoint( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "universalJoint" ) ) {
			if ( !ParseUniversalJoint( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "hinge" ) ) {
			if ( !ParseHinge( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "slider" ) ) {
			if ( !ParseSlider( src ) ) {
				return false;
			}
		} else if ( !token.Icmp( "spring" ) ) {
			if ( !ParseSpring( src ) ) {
				return false;
			}
		} else if ( token == "}" ) {
			break;
		} else {
			src.Error( "unknown keyword %s", token.c_str() );
			return false;
		}
	}

	for ( i = 0; i < bodies.Num(); i++ ) {
		// check for multiple bodies with the same name
		for ( j = i+1; j < bodies.Num(); j++ ) {
			if ( bodies[i]->name == bodies[j]->name ) {
				src.Error( "two bodies with the same name \"%s\"", bodies[i]->name.c_str() );
			}
		}
	}

	for ( i = 0; i < constraints.Num(); i++ ) {
		// check for multiple constraints with the same name
		for ( j = i+1; j < constraints.Num(); j++ ) {
			if ( constraints[i]->name == constraints[j]->name ) {
				src.Error( "two constraints with the same name \"%s\"", constraints[i]->name.c_str() );
			}
		}
		// check if there are two valid bodies set
		if ( constraints[i]->body1 == "" ) {
			src.Error( "no valid body1 specified for constraint '%s'", constraints[i]->name.c_str() );
		}
		if ( constraints[i]->body2 == "" ) {
			src.Error( "no valid body2 specified for constraint '%s'", constraints[i]->name.c_str() );
		}
	}

	// make sure the body which modifies the origin comes first
	for ( i = 0; i < bodies.Num(); i++ ) {
		if ( bodies[i]->jointName == "origin" ) {
			if ( i != 0 ) {
				idDeclAF_Body *b = bodies[0];
				bodies[0] = bodies[i];
				bodies[i] = b;
			}
			break;
		}
	}

	(void)noCaching;
	return true;
}

/*
================
idDeclAF::DefaultDefinition
================
*/
const char *idDeclAF::DefaultDefinition( void ) const {
	return
		"{\n"
	"\t"	"settings {\n"
	"\t\t"		"model \"\"\n"
	"\t\t"		"skin \"\"\n"
	"\t\t"		"friction 0.01, 0.01, 0.8, 0.5\n"
	"\t\t"		"suspendSpeed 20, 30, 40, 60\n"
	"\t\t"		"noMoveTime 1\n"
	"\t\t"		"noMoveTranslation 10\n"
	"\t\t"		"noMoveRotation 10\n"
	"\t\t"		"minMoveTime -1\n"
	"\t\t"		"maxMoveTime -1\n"
	"\t\t"		"totalMass -1\n"
	"\t\t"		"contents corpse\n"
	"\t\t"		"clipMask solid, corpse\n"
	"\t\t"		"selfCollision 1\n"
	"\t"	"}\n"
	"\t"	"body \"body\" {\n"
	"\t\t"		"joint \"origin\"\n"
	"\t\t"		"mod orientation\n"
	"\t\t"		"model box( ( -10, -10, -10 ), ( 10, 10, 10 ) )\n"
	"\t\t"		"origin ( 0, 0, 0 )\n"
	"\t\t"		"density 0.2\n"
	"\t\t"		"friction 0.01, 0.01, 0.8\n"
	"\t\t"		"contents corpse\n"
	"\t\t"		"clipMask solid, corpse\n"
	"\t\t"		"selfCollision 1\n"
	"\t\t"		"containedJoints \"*origin\"\n"
	"\t"	"}\n"
		"}\n";
}

/*
================
idDeclAF::FreeData
================
*/
void idDeclAF::FreeData( void ) {
	modified = false;
	defaultLinearFriction = 0.01f;
	defaultAngularFriction = 0.01f;
	defaultContactFriction = 0.8f;
	defaultConstraintFriction = 0.5f;
	totalMass = -1;
	suspendVelocity.Set( 20.0f, 30.0f );
	suspendAcceleration.Set( 40.0f, 60.0f );
	noMoveTime = 1.0f;
	noMoveTranslation = 10.0f;
	noMoveRotation = 10.0f;
	minMoveTime = -1.0f;
	maxMoveTime = -1.0f;
	selfCollision = true;
	fastEval = false;
	contents = CONTENTS_CORPSE;
	clipMask = CONTENTS_SOLID | CONTENTS_CORPSE;
	bodies.DeleteContents( true );
	constraints.DeleteContents( true );
}

/*
================
idDeclAF::Finish
================
*/
void idDeclAF::Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const {
	int i;

	const char *name = GetName();
	for ( i = 0; i < bodies.Num(); i++ ) {
		idDeclAF_Body *body = bodies[i];
		body->v1.Finish( name, GetJointTransform, frame, model );
		body->v2.Finish( name, GetJointTransform, frame, model );
		body->origin.Finish( name, GetJointTransform, frame, model );
		body->frictionDirection.Finish( name, GetJointTransform, frame, model );
		body->contactMotorDirection.Finish( name, GetJointTransform, frame, model );
	}
	for ( i = 0; i < constraints.Num(); i++ ) {
		idDeclAF_Constraint *constraint = constraints[i];
		constraint->anchor.Finish( name, GetJointTransform, frame, model );
		constraint->anchor2.Finish( name, GetJointTransform, frame, model );
		constraint->shaft[0].Finish( name, GetJointTransform, frame, model );
		constraint->shaft[1].Finish( name, GetJointTransform, frame, model );
		constraint->axis.Finish( name, GetJointTransform, frame, model );
		constraint->limitAxis.Finish( name, GetJointTransform, frame, model );
	}
}

/*
================
idDeclAF::NewBody
================
*/
void idDeclAF::NewBody( const char *name ) {
	idDeclAF_Body *body;

	body = new idDeclAF_Body();
	body->SetDefault( this );
	body->name = name;
	bodies.Append( body );
}

/*
================
idDeclAF::RenameBody

  rename the body with the given name and rename
  all constraint body references
================
*/
void idDeclAF::RenameBody( const char *oldName, const char *newName ) {
	int i;

	for ( i = 0; i < bodies.Num(); i++ ) {
		if ( bodies[i]->name.Icmp( oldName ) == 0 ) {
			bodies[i]->name = newName;
			break;
		}
	}
	for ( i = 0; i < constraints.Num(); i++ ) {
		if ( constraints[i]->body1.Icmp( oldName ) == 0 ) {
			constraints[i]->body1 = newName;
		} else if ( constraints[i]->body2.Icmp( oldName ) == 0 ) {
			constraints[i]->body2 = newName;
		}
	}
}

/*
================
idDeclAF::DeleteBody

  delete the body with the given name and delete
  all constraints that reference the body
================
*/
void idDeclAF::DeleteBody( const char *name ) {
	int i;

	for ( i = 0; i < bodies.Num(); i++ ) {
		if ( bodies[i]->name.Icmp( name ) == 0 ) {
			delete bodies[i];
			bodies.RemoveIndex( i );
			break;
		}
	}
	for ( i = 0; i < constraints.Num(); i++ ) {
		if ( constraints[i]->body1.Icmp( name ) == 0 ||
			constraints[i]->body2.Icmp( name ) == 0 ) {
			delete constraints[i];
			constraints.RemoveIndex( i );
			i--;
		}
	}
}

/*
================
idDeclAF::NewConstraint
================
*/
void idDeclAF::NewConstraint( const char *name ) {
	idDeclAF_Constraint *constraint;

	constraint = new idDeclAF_Constraint;
	constraint->SetDefault( this );
	constraint->name = name;
	constraints.Append( constraint );
}

/*
================
idDeclAF::RenameConstraint
================
*/
void idDeclAF::RenameConstraint( const char *oldName, const char *newName ) {
	int i;

	for ( i = 0; i < constraints.Num(); i++ ) {
		if ( constraints[i]->name.Icmp( oldName ) == 0 ) {
			constraints[i]->name = newName;
			return;
		}
	}
}

/*
================
idDeclAF::DeleteConstraint
================
*/
void idDeclAF::DeleteConstraint( const char *name ) {
	int i;

	for ( i = 0; i < constraints.Num(); i++ ) {
		if ( constraints[i]->name.Icmp( name ) == 0 ) {
			delete constraints[i];
			constraints.RemoveIndex( i );
			return;
		}
	}
}

/*
================
idDeclAF::idDeclAF
================
*/
idDeclAF::idDeclAF( void ) {
	FreeData();
}

/*
================
idDeclAF::~idDeclAF
================
*/
idDeclAF::~idDeclAF( void ) {
	bodies.DeleteContents( true );
	constraints.DeleteContents( true );
}

/*
================
idDeclAF::Validate
================
*/
bool idDeclAF::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const {
	(void)strReportTo;

	idDecl *decl = declManager->AllocateDecl( DECL_AF );
	const bool valid = DeclManager_ValidateParsedDecl( decl, DECL_AF, decl != NULL && decl->Parse( psText, iTextLength, false ) );
	if ( decl != NULL ) {
		decl->FreeData();
	}
	DeclManager_FreeAllocatedDecl( decl );
	return valid;
}

class rvDeclAFEditLocal : public rvDeclAFEdit {
public:
	bool Save(idDeclAF* edit) override {
		return edit != NULL && edit->Save();
	}

	void NewBody(idDeclAF* edit, const char* name) override {
		if (edit != NULL) {
			edit->NewBody(name);
		}
	}

	void RenameBody(idDeclAF* edit, const char* oldName, const char* newName) override {
		if (edit != NULL) {
			edit->RenameBody(oldName, newName);
		}
	}

	void DeleteBody(idDeclAF* edit, const char* name) override {
		if (edit != NULL) {
			edit->DeleteBody(name);
		}
	}

	void NewConstraint(idDeclAF* edit, const char* name) override {
		if (edit != NULL) {
			edit->NewConstraint(name);
		}
	}

	void RenameConstraint(idDeclAF* edit, const char* oldName, const char* newName) override {
		if (edit != NULL) {
			edit->RenameConstraint(oldName, newName);
		}
	}

	void DeleteConstraint(idDeclAF* edit, const char* name) override {
		if (edit != NULL) {
			edit->DeleteConstraint(name);
		}
	}
};

static rvDeclAFEditLocal localDeclAFEdit;
rvDeclAFEdit* declAFEdit = &localDeclAFEdit;
