////////////////////////////////////////////////////////////////////////////////
// BipedMech.cpp
// BipedMech implementation
// Copyright Bjoern Ganster 
// Development started in 2007
////////////////////////////////////////////////////////////////////////////////

#include "BipedMech.h"
#include "GroundVehicleAI.h"
#include "intersections.h"
#include "MechSim.h"
#include "MWBase.h"
#include "VehicleAI.h"

#include <iostream>

#define FILE_NUMBER FILE_NO_BIPEDMECH_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

BipedMech::BipedMech()
: Vehicle("BipedMech", BipedMechID, nullptr, Biped_Mech_MAX_PARTS, "H T A L"),
  legsParallelToGround (false),
  torsoTilt(0.0),
  torsoTwist(0.0),
  torsoTwistLimit(90.0),
  torsoTiltLimit(30.0),
  armTwist(0.0),
  armTwistLimit(0.0),
  mGyroHits(0),
  mLifeSupportDestroyed(false),
  mJumpJetHits(0),
  hideTorsoInCockpitView(false),
  mSensorDamage(0)
{
	SET_SCOPE;
	mArmorDamageFront.resize(Biped_Mech_Sections);
	mArmorDamageRear.resize(Biped_Mech_Sections);
	mInternalDamage.resize(Biped_Mech_Sections);
	mSections.resize(Biped_Mech_Sections);

	const int BipedCriticalCount[Biped_Mech_Sections] = { 6, 12, 12, 12, 12, 12, 6, 6 };

	for (int i = 0; i < Biped_Mech_Sections; i++) {
		mFrontArmor[i] = 0;
		mRearArmor[i] = 0;
		mInternalStructure[i] = 0;
		mSections[i].sectionName = tr(i + 4);
		mSections[i].criticals.insert(mSections[i].criticals.begin(), BipedCriticalCount[i], WeaponType::Unused_Critical_Slot);
	}

	mCriticalDamaged.resize(Biped_Mech_Sections);
	for (int i = 0; i < Biped_Mech_Sections; i++) {
		for (int j = 0; j < getCriticalCount(i); j++) {
			mCriticalDamaged[i].push_back(false);
		}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

BipedMech::~BipedMech()
{
}

////////////////////////////////////////////////////////////////////////////////
// Add anim parameters

void BipedMech::addAnim (XMLTreeConstIterator& iter)
{
	SET_SCOPE;
	MechAnimation animationData;
	animationData.animDuration = iter.getDoubleAttributeByName("duration");
	animationData.metersPerSecond = iter.getDoubleAttributeByName("metersPerSecond");

	for (size_t i = 0; i < iter.getChildCount(); ++i) {
		iter.goToChild(i);
		double phase = iter.getDoubleAttributeByName("phase");
		double ulegAngle = iter.getDoubleAttributeByName("ulegAngle");
		double llegAngle = iter.getDoubleAttributeByName("llegAngle");
		double footAngle = iter.getDoubleAttributeByName("footAngle");

		animationData.data.emplace_back(phase);
		animationData.data.emplace_back(ulegAngle);
		animationData.data.emplace_back(llegAngle);
		animationData.data.emplace_back(footAngle);

		int parallelFeetVal = 0;
		iter.getIntAttributeValueByName("parallelFeet", parallelFeetVal);
		if (parallelFeetVal != 0) {
			legsParallelToGround = true;
		}
		iter.goToParent();
	}

	allAnimations.push_back(animationData);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Load mech from MVMK/XML

void BipedMech::readSpecialAttributesFromRoot(XMLTreeConstIterator& iter)
{
	SET_SCOPE;
	torsoTwistLimit = iter.getDoubleAttributeByName("torsoTwistLimit", 90.0);
	torsoTiltLimit = iter.getDoubleAttributeByName("torsoTiltLimit", 30.0);
	armTwistLimit = iter.getDoubleAttributeByName("armTwistLimit", 30.0);
	auto hideTorsoInCockpitViewInt = iter.getIntAttributeValueByName("hideTorsoInCockpitView", 0);

	if (hideTorsoInCockpitViewInt > 0) {
		hideTorsoInCockpitView = true;
	}

	RETURN;
}

void BipedMech::readSpecialAttributes(XMLTreeConstIterator& iter, const std::vector<RenderableObject*>& geometries, const PathChar* mechDir)
{
	// Load mech parts
	SET_SCOPE;
	BGCPString nodeName;
	iter.getNodeName(nodeName);

	if (nodeName == "hip")
		placePart(iter, geometries, Biped_Mech_Part_Hip, mechDir);
	else if (nodeName == "torso")
		placePart(iter, geometries, Biped_Mech_Part_Torso, mechDir);
	else if (nodeName == "LeftArm")
		placePart(iter, geometries, Biped_Mech_Part_LeftArm, mechDir);
	else if (nodeName == "RightArm")
		placePart(iter, geometries, Biped_Mech_Part_RightArm, mechDir);
	else if (nodeName == "LeftUpperLeg")
		placePart(iter, geometries, Biped_Mech_Part_LeftUpperLeg, mechDir);
	else if (nodeName == "LeftLowerLeg")
		placePart(iter, geometries, Biped_Mech_Part_LeftLowerLeg, mechDir);
	else if (nodeName == "LeftFoot")
		placePart(iter, geometries, Biped_Mech_Part_LeftFoot, mechDir);
	else if (nodeName == "RightUpperLeg")
		placePart(iter, geometries, Biped_Mech_Part_RightUpperLeg, mechDir);
	else if (nodeName == "RightLowerLeg")
		placePart(iter, geometries, Biped_Mech_Part_RightLowerLeg, mechDir);
	else if (nodeName == "RightFoot")
		placePart(iter, geometries, Biped_Mech_Part_RightFoot, mechDir);
	else if (nodeName == "windshield")
		placePart(iter, geometries, Biped_Mech_Part_Windshield, mechDir);
	else if (nodeName == "LeftLowerArm") {
		placePart(iter, geometries, Biped_Mech_Part_LeftLowerArm, mechDir);
		preferLowerArms = true;
	} else if (nodeName == "RightLowerArm") {
		placePart(iter, geometries, Biped_Mech_Part_RightLowerArm, mechDir);
		preferLowerArms = true;
	} else if (nodeName == "Cockpit") {
		placePart(iter, geometries, Biped_Mech_Part_Cockpit, mechDir);
	} else if (nodeName == "anim") {
		addAnim (iter);
	} else if (nodeName == "critical-layout") {
		// Defines where to place weapons in 3D
		criticalLayouts.resize(Biped_Mech_Sections);
		for (size_t k = 0; k < iter.getChildCount(); k++) {
			iter.goToChild(k);
			int location = -1;
			if (iter.nodeNameEquals("head")) {
				location = Biped_Mech_Section_Head;
			} else if (iter.nodeNameEquals("center-torso")) {
				location = Biped_Mech_Section_Center_Torso;
			} else if (iter.nodeNameEquals("left-torso")) {
				location = Biped_Mech_Section_Left_Torso;
			} else if (iter.nodeNameEquals("right-torso")) {
				location = Biped_Mech_Section_Right_Torso;
			} else if (iter.nodeNameEquals("right-arm")) {
				location = Biped_Mech_Section_Right_Arm;
			} else if (iter.nodeNameEquals("left-arm")) {
				location = Biped_Mech_Section_Left_Arm;
			} else if (iter.nodeNameEquals("right-leg")) {
				location = Biped_Mech_Section_Right_Leg;
			} else if (iter.nodeNameEquals("left-leg")) {
				location = Biped_Mech_Section_Left_Leg;
			}
			if (location >= 0 && location < Biped_Mech_Sections) {
				criticalLayouts[location].start.x = iter.getDoubleAttributeByName("sx");
				criticalLayouts[location].start.y = iter.getDoubleAttributeByName("sy");
				criticalLayouts[location].start.z = iter.getDoubleAttributeByName("sz");
				criticalLayouts[location].direction.x = iter.getDoubleAttributeByName("dx");
				criticalLayouts[location].direction.y = iter.getDoubleAttributeByName("dy");
				criticalLayouts[location].direction.z = iter.getDoubleAttributeByName("dz");
			}
			iter.goToParent();
		}
	}

	RETURN;
}

double animate_old(int part, const MechAnimation& animationData, double phase)
{
	if ((int)animationData.data.size() < part+4) {
		return 0.0;
	}

	// Initialize with first keyframe of the animation
	SET_SCOPE;
    phase = std::fmod(phase, animationData.animDuration);
    auto basePhase = animationData.data[0];
    auto angle1 = animationData.data[part];
    auto angle2 = animationData.data[part+4];
    double phaseDuration = animationData.animDuration;
    if (animationData.data.size() > 4) {
        phaseDuration = animationData.data[4]-animationData.data[0];
    }

	// Check if one of the following keyframes applies to phase
    size_t i = 4;
    while (i+4 < animationData.data.size() && animationData.data[i] < phase) {
        if (animationData.data[i] < phase) {
            basePhase = animationData.data[i];
            angle1 = animationData.data[i + part];
            angle2 = animationData.data[i + part + 4];
            phaseDuration = animationData.data[i+4]-animationData.data[i];
            i += 4;
        }
    }

	// Check if the last keyframe applies
    if (animationData.data[i] < phase) {
        basePhase = animationData.data[i];
        angle1 = animationData.data[i + part];
        angle2 = animationData.data[part];
        phaseDuration = animationData.animDuration-animationData.data[i];
    }

	// Interpolate between the angles in the keyframe
    RETURN angle1 + (angle2-angle1)*(phase-basePhase)/phaseDuration;
}

double BipedMech::animate(int part, double phase, double speed, double phaseShift)
{
	if (allAnimations.size() == 0) {
		return 0.0;
	}

	// Find the nearest matching faster and slower animations
	SET_SCOPE;
	int fasterAnimationIndex = -1;
	int slowerAnimationIndex = -1;

	for (size_t i = 0; i < allAnimations.size(); ++i) {
		if (allAnimations[i].metersPerSecond >= speed
		&&  (fasterAnimationIndex  < 0 || allAnimations[fasterAnimationIndex].metersPerSecond > allAnimations[i].metersPerSecond))
		{
			fasterAnimationIndex = i;
		}
		if (allAnimations[i].metersPerSecond <= speed
		&&  (slowerAnimationIndex < 0 || allAnimations[slowerAnimationIndex].metersPerSecond < allAnimations[i].metersPerSecond))
		{
			slowerAnimationIndex = i;
		}
	}

	if (fasterAnimationIndex >= 0 && slowerAnimationIndex >= 0 && fasterAnimationIndex != slowerAnimationIndex) {
		// Blend faster and slower animation
		// todo: in order to interpolate between animations with different durations, compute fastPhase and slowPhase
		// based on the duration of those animations
		SET_OP;
		double x = (speed - allAnimations[slowerAnimationIndex].metersPerSecond)
			/ (allAnimations[fasterAnimationIndex].metersPerSecond - allAnimations[slowerAnimationIndex].metersPerSecond);
		auto fastDuration = allAnimations[fasterAnimationIndex].animDuration;
		auto slowDuration = allAnimations[slowerAnimationIndex].animDuration;
		double resultingAnimationDuration = slowDuration + x * (fastDuration - slowDuration);
		double resultingAnimationFraction = phase / resultingAnimationDuration;
		resultingAnimationFraction -= (int) resultingAnimationFraction;
		double fastPhase = (resultingAnimationFraction + phaseShift) * allAnimations[fasterAnimationIndex].animDuration;
		double slowPhase = (resultingAnimationFraction + phaseShift) * allAnimations[slowerAnimationIndex].animDuration;
		double fastValue = animate_old(part, allAnimations[fasterAnimationIndex], fastPhase);
		double slowValue = animate_old(part, allAnimations[slowerAnimationIndex], slowPhase);
		RETURN slowValue + x * (fastValue - slowValue);
	} else {
		// Use the one available animation
		SET_OP;
		int theChosenOne = std::max(std::max(fasterAnimationIndex, slowerAnimationIndex), 0);
		phase += phaseShift * allAnimations[theChosenOne].animDuration;
		RETURN animate_old(part, allAnimations[theChosenOne], phase);
	}
}

void BipedMech::setMatrices(double speed, DWORD missionTime)
{
	SET_SCOPE;
	Point3D pos = getPosition();
	Matrix M = TranslationMatrix(pos.x, pos.y, pos.z) * Matrix(p_left, p_up, p_front);

	SET_OP;
	if (vehicleStatus != engineRunning) {
		for (size_t i = 0; i < getObjectCount(); ++i) {
			RenderableObject* ro = getObject(i);
			ro->setMatrix(M);
		}
		RETURN;
	}

	SET_OP;
	Matrix hipMatrix = setLimbMatrix(Biped_Mech_Part_Hip, M, torsoTilt, 1, 0, 0);
	Matrix torsoMatrix = setLimbMatrix(Biped_Mech_Part_Torso, hipMatrix, torsoTwist, 0, 1, 0);
	setMatrixIfNN(getObject(Biped_Mech_Part_Cockpit), torsoMatrix);
	setMatrixIfNN(getObject(Biped_Mech_Part_Windshield), torsoMatrix);
	setMatrixIfNN(getObject(Biped_Mech_Part_LeftArm), torsoMatrix);
	setMatrixIfNN(getObject(Biped_Mech_Part_RightArm), torsoMatrix);

	SET_OP;
	Matrix LLArmMatrix = setLimbMatrix(Biped_Mech_Part_LeftLowerArm, torsoMatrix, armTwist, 0, 1, 0);
	Matrix RLArmMatrix = setLimbMatrix(Biped_Mech_Part_RightLowerArm, torsoMatrix, armTwist, 0, 1, 0);

	const double time = missionTime / 1000.0;
	double leftUpperLegAngle = animate(1, time, speed, 0.0);
	Matrix LeftUpperLegMatrix = setLimbMatrix(Biped_Mech_Part_LeftUpperLeg, M, leftUpperLegAngle, 1, 0, 0);

	double leftLowerLegAngle = animate(2, time, speed, 0.0);
	Matrix LeftLowerLegMatrix = setLimbMatrix(Biped_Mech_Part_LeftLowerLeg, LeftUpperLegMatrix, leftLowerLegAngle, 1, 0, 0);

	double leftFootAngle;
	if (legsParallelToGround) {
		leftFootAngle = -leftUpperLegAngle - leftLowerLegAngle;
	} else {
		leftFootAngle = animate(3, time, speed, 0.0);
	}
	Matrix LeftFootMatrix = setLimbMatrix(Biped_Mech_Part_LeftFoot, LeftLowerLegMatrix, leftFootAngle, 1, 0, 0);

	SET_OP;
	double rightUpperLegAngle = animate(1, time, speed, 0.5);
	Matrix rightUpperLegMatrix = setLimbMatrix(Biped_Mech_Part_RightUpperLeg, M, rightUpperLegAngle, 1, 0, 0);

	double rightLowerLegAngle = animate(2, time, speed, 0.5);
	Matrix rightLowerLegMatrix = setLimbMatrix(Biped_Mech_Part_RightLowerLeg, rightUpperLegMatrix, rightLowerLegAngle, 1, 0, 0);

	double rightFootAngle;
	if (legsParallelToGround) {
		rightFootAngle = -rightUpperLegAngle - rightLowerLegAngle;
	} else {
		rightFootAngle = animate(3, time, speed, 0.5);
	}
	setLimbMatrix(Biped_Mech_Part_RightFoot, rightLowerLegMatrix, rightFootAngle, 1, 0, 0);
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Calculate actual mech weight

int BipedMech::countArmorPoints() const
{
	SET_SCOPE;
	int armorPoints = mRearArmor[Biped_Mech_Section_Center_Torso] +
		mRearArmor[Biped_Mech_Section_Left_Torso] +
		mRearArmor[Biped_Mech_Section_Right_Torso];

	for (int i = 0; i < 8; i++)  {
		armorPoints += mFrontArmor[i];
	}

	RETURN armorPoints;
}

double BipedMech::calcInternalStructureWeight() const
{
	if (InternalStructureType == WeaponType::Unused_Critical_Slot) {
		return tonnage / 10.0;
	} else {
		return tonnage / 20.0;
	}
}

double BipedMech::calcJJWeight () 
{
	SET_SCOPE;
	jumpJets = countWeaponInAllSections(WeaponType::CE_Jump_Jet);

	if (tonnage > 85) {
		RETURN 2*jumpJets;
	}else if (tonnage > 55) {
		RETURN jumpJets;
	} else {
		RETURN jumpJets / 2.0;
	}
}

double BipedMech::calcRealWeight()
{
	// Add other equipment
	SET_SCOPE;
	double equipmentTonnage = 0;
	for (int i = 0; i < Biped_Mech_Sections; i++) {
		for (int j = 0; j < getCriticalCount (i); j++) {
			WeaponType crit = getCriticals(i, j);
			const MWEquipment* weapon = getWeaponStats (crit);
			if (weapon != NULL)
				if (weapon->TonnageTimes100 > 0.0)
					equipmentTonnage += weapon->TonnageTimes100 / (100.0*weapon->Criticals);
		}
	}

	SET_OP;
	double engineWeight = getEngineWeight (engineRating, EngineType);
	double internalStructureWeight = calcInternalStructureWeight();
	double armorWeight = calcArmorWeight();
	double gyroWeight = (engineRating + 95) / 100;
	double HeatSinkWeight = std::max(heatSinks-10,0);
	double cockpitWeight = 3.0;
	double jjWeight = calcJJWeight();

	int MASCTonsAndSlots = calcMASCTonsAndSlots();
	int IS_MASC_slots = countWeaponInAllSections (WeaponType::IS_MASC);
	int Clan_MASC_slots = countWeaponInAllSections (WeaponType::Clan_MASC);

	if (IS_MASC_slots < MASCTonsAndSlots
	&&  Clan_MASC_slots < MASCTonsAndSlots)
	{
		MASCTonsAndSlots = 0;
	}

	SET_OP;
	realWeight = engineWeight + internalStructureWeight + gyroWeight +
		armorWeight + equipmentTonnage + cockpitWeight +
		HeatSinkWeight + jjWeight + MASCTonsAndSlots;

	RETURN realWeight;
}

////////////////////////////////////////////////////////////////////////////////
// Add weapon to mech section

// todo: overlap with Vehicle::adWeapon?
bool BipedMech::addWeapon(int currSection, WeaponType weaponNo)
{
	SET_SCOPE;
	const MWEquipment* weapon = getWeaponStats(weaponNo);

	if (weapon == NULL
	||  weaponNo == CE_Foot) 
	{
		RETURN false;
	}

	int freeCriticals = countWeaponCriticals (currSection, WeaponType::Unused_Critical_Slot);

	if (weaponNo == WeaponType::IS_Heat_Sink
	||  weaponNo == WeaponType::IS_Double_Heat_Sink
	||  weaponNo == WeaponType::Clan_Double_Heat_Sink)
	{
		SET_OP;
		if (weaponNo != getHeatSinkType()) {
			printf ("Incompatible heat sink type\n");
			RETURN false;
		}

		if (heatSinks < engineRating / 25) {
			// Heat sink that does not need critical space, add and abort
			++heatSinks;
			printf("Internal heat sink added - %i\n", heatSinks);
			RETURN true;
		} else {
			if (freeCriticals >= weapon->Criticals) {
				// Heat sink that needs normal critical space, treated normally
				++heatSinks;
				printf("External heat sink added - %i\n", heatSinks);
			} else {
				// Heat sink needs critical space, but no criticals available
				RETURN false;
			}
		}
	}

	// Add criticals
	SET_OP;
	if (freeCriticals < weapon->Criticals) {
		RETURN false;
	}

	MountedWeapon wref;
	wref.weaponType = weaponNo;
	wref.group = 0;
	wref.criticals.clear();

	int added = 0;
	int i = 0;
	int max = (int)getCriticalCount(currSection);

	while (i < max && added < weapon->Criticals) {
		if (getCriticals(currSection, i) == WeaponType::Unused_Critical_Slot) {
			setCriticals(currSection, i, weaponNo);

			wref.section = currSection;
			wref.criticals.push_back(i);

			++added;
		} 
		++i;
	}

	if ((weapon->flags & MWWeaponFlag_IncludeInWeaponList) > 0) {
		weapons.emplace_back(wref);
	}
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////
// Remove weapon

int BipedMech::removeWeapon(int currSection, int critNo, WeaponType weaponNo, int critCount)
{
	SET_SCOPE;
	if (weaponNo == WeaponType::IS_Heat_Sink
	||  weaponNo == WeaponType::IS_Double_Heat_Sink
	||  weaponNo == WeaponType::Clan_Double_Heat_Sink)
	{
		--heatSinks;
		COUT << "External heat sink removed" << std::endl;
	}

	int removed = 0;
	int i = critNo;
	int max = getCriticalCount(currSection);

	if (critCount == -1) {
		const MWEquipment* weapon = getWeaponStats(weaponNo);
		if (weapon == NULL) {
			RETURN 0;
		}
		critCount = weapon->Criticals;
	}

	int oneCrit = -1;
	while (removed < critCount 
	&&     i < 2*max) // test against twice crit count if tests start in last critical
	{
		SET_OP;
		if (getCriticals(currSection, i%max) == weaponNo) {
			setCriticals(currSection, i%max, WeaponType::Unused_Critical_Slot);
			oneCrit = i%max;
			++removed;
		} 
		++i;
	}

	// Check and update weapon systems table
	if (removed > 0 && oneCrit >= 0) {
		SET_OP;
		auto it = weapons.begin();
		while (it < weapons.end()) {
			auto& weapon = *it;
			size_t j = 0;
			while (j < weapon.criticals.size()) {
				if (weapon.section == currSection 
					&&  weapon.criticals[j] == oneCrit)
				{
					it =weapons.erase(it);
					RETURN removed;
				} else
					++j;
			}
			++it;
		}
	}

	RETURN removed;
}

////////////////////////////////////////////////////////////////////////////////

int BipedMech::checkAndFixEngineCrits(int section)
{
	SET_SCOPE;
	int engineCritsCurrentlyInSection = countWeaponCriticals (section, WeaponType::CE_Engine);
	int reqSideTorsoSlots = 3;
	if (techType == Tech_Type_Clan)
		reqSideTorsoSlots = 2;

	if (engineCritsCurrentlyInSection == reqSideTorsoSlots) {
		RETURN 0;
	}

	int unusedCritsInSection = countWeaponCriticals (section, WeaponType::Unused_Critical_Slot);
	if (reqSideTorsoSlots - engineCritsCurrentlyInSection < unusedCritsInSection) {
		// Add missing side torso engine slots
		while (engineCritsCurrentlyInSection < reqSideTorsoSlots) {
			addWeapon (section, WeaponType::CE_Engine);
			++engineCritsCurrentlyInSection;
		}
		RETURN 0;
	} else {
		// Can't add missing side torso slots because there not enough free criticals
		RETURN 27;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Check - and try to correct - config problems
// Returns true on success, false otherwise, and sets an error description on failure

bool BipedMech::check(BGCPString& errors)
{
	// Overweight? Allow for floating point errors ...
	SET_SCOPE;
	if (realWeight > tonnage + 0.001) {
		errors = tr(26);
		RETURN false;
	}

	// XL engine criticals in side torsi?
	if (getInteger(Info_EngineType) == ENGINE_TYPE_XL) {
		int engRes = checkAndFixEngineCrits(Biped_Mech_Section_Left_Torso);
		if (engRes > 0) {
			errors = tr(engRes);
			RETURN false;
		}

		engRes = checkAndFixEngineCrits(Biped_Mech_Section_Right_Torso);
		if (engRes > 0) {
			errors = tr(engRes);
			RETURN false;
		}
	}

	// Check for correct number of endo steel internals
	int endoCritsIS = countWeaponInAllSections(WeaponType::IS_Endo_Steel);
	if (endoCritsIS != 0   && endoCritsIS != 14) {
		errors = tr(36) + BGCPString(" (") + BGCPString::fromInt(endoCritsIS) + BGCPString("/14)");
		RETURN false;
	}
	int endoCritsClan = countWeaponInAllSections(WeaponType::Clan_Endo_Steel_Internals);
	if (endoCritsClan != 0 && endoCritsClan != 7) {
		errors = tr(36) + BGCPString(" (") + BGCPString::fromInt(endoCritsClan) + BGCPString("/7)");
		RETURN false;
	}

	// Check for correct number of endo steel internals
	int ffCritsIS = countWeaponInAllSections(WeaponType::IS_Ferro_Fibrous);
	if (ffCritsIS != 0   && ffCritsIS != 14) {
		errors = tr(37) + BGCPString(" (") + BGCPString::fromInt(ffCritsIS) + BGCPString("/14)");
		RETURN false;
	}
	int ffCritsClan = countWeaponInAllSections(WeaponType::Clan_Ferro_Fibrous);
	if (ffCritsClan != 0 && ffCritsClan != 7) {
		errors = tr(37) + BGCPString("( ") + BGCPString::fromInt(ffCritsClan) + BGCPString("/7)");
		RETURN false;
	}

	// Check if the jump jets are less or equals to the walking movement rate
	int jjCrits = countWeaponInAllSections(WeaponType::CE_Jump_Jet);
	if (tonnage > 0) {
		if (jjCrits > getInteger (Info_EngineRating) / tonnage) {
			errors = tr(43);
			RETURN false;
		}
	}

	if (heatSinks < 10) {
		errors = tr(50);
		RETURN false;
	}

	WeaponType hsType = getHeatSinkType();
	if (hsType != IS_Heat_Sink
	&&  hsType != IS_Double_Heat_Sink
	&&  hsType != Clan_Double_Heat_Sink)
	{
		errors = tr(63);
		RETURN false;
	}

	// Check correct number of weapon criticals
	for (int weaponNo = IS_Energy_Weapon_Base; weaponNo < WeaponsCount; ++weaponNo) {
		const MWEquipment* weaponStats = getWeaponStats((WeaponType) weaponNo);
		if (weaponStats != nullptr
		&&  weaponStats->Criticals > 1)
		{
			int totalCrits = countWeaponInAllSections((WeaponType) weaponNo);
			int surplusCrits = totalCrits % weaponStats->Criticals;
			if (surplusCrits) {
				errors = tr(62) + weaponStats->longName;
				RETURN false;
			}
		}
	}

	int heatSinksThatRequireCriticals = heatSinks - engineRating / 25;
	if (HeatSinkType == WeaponType::IS_Heat_Sink) {
		int requiredHeatSinkCriticals = heatSinksThatRequireCriticals;
		int heatSinkCrits = countWeaponInAllSections(WeaponType::IS_Heat_Sink);
		if (requiredHeatSinkCriticals < heatSinkCrits && requiredHeatSinkCriticals > 0) {
			errors = tr(51);
			RETURN false;
		}
		if (requiredHeatSinkCriticals > heatSinkCrits) {
			errors = tr(52);
			RETURN false;
		}
	} else if (HeatSinkType == WeaponType::IS_Double_Heat_Sink) {
		int requiredHeatSinkCriticals = 3 * heatSinksThatRequireCriticals;
		int heatSinkCrits = countWeaponInAllSections(WeaponType::IS_Double_Heat_Sink);
		if (requiredHeatSinkCriticals < heatSinkCrits && requiredHeatSinkCriticals > 0) {
			errors = tr(51);
			RETURN false;
		}
		if (requiredHeatSinkCriticals > heatSinkCrits) {
			errors = tr(52);
			RETURN false;
		}
	} else if (HeatSinkType == WeaponType::Clan_Double_Heat_Sink) {
		int requiredHeatSinkCriticals = 2 * heatSinksThatRequireCriticals;
		int heatSinkCrits = countWeaponInAllSections(WeaponType::Clan_Double_Heat_Sink);
		if (requiredHeatSinkCriticals < heatSinkCrits && requiredHeatSinkCriticals > 0) {
			errors = tr(51);
			RETURN false;
		}
		if (requiredHeatSinkCriticals > heatSinkCrits) {
			errors = tr(52);
			RETURN false;
		}
	}

	// No errors found
	errors.clear();
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////

DWORD BipedMech::getArmor(int section, int ArmorType) const
{
   if (section < Biped_Mech_Sections) {
      if (ArmorType == Vehicle::Armor_Front)
         return mFrontArmor[section];
      else if (ArmorType == Vehicle::Armor_Rear)
         return mRearArmor[section];
   }

   return 0;
}

////////////////////////////////////////////////////////////////////////////////

bool BipedMech::setArmor(int section, DWORD newVal, int ArmorType)
{ 
   bool maySet = false;

   if (ArmorType == Armor_Front) {
      if (section == Biped_Mech_Section_Center_Torso
      ||  section ==  Biped_Mech_Section_Left_Torso
      ||  section ==  Biped_Mech_Section_Right_Torso)
      {
         if ((int) newVal + mRearArmor[section] <= 2* mInternalStructure[section])
            maySet = true;
      } else if (section == Biped_Mech_Section_Head) {
         if (newVal < 10)
            maySet = true;
      } else {
         if ((int) newVal <= 2* mInternalStructure[section])
            maySet = true;
      }
   } else {
      if (section == Biped_Mech_Section_Center_Torso
      ||  section ==  Biped_Mech_Section_Left_Torso
      ||  section ==  Biped_Mech_Section_Right_Torso)
      {
         if ((int) newVal + mFrontArmor[section] <= 2* mInternalStructure[section])
            maySet = true;
      }
   }

   if (maySet) {
      if (ArmorType == Armor_Front) {
         mFrontArmor[section] = newVal;
      } else {
         mRearArmor[section] = newVal;
      }
      return true;
   } else
      return false;
}

void BipedMech::setInternalStructure (int section, DWORD newVal)
{
   if (section < Biped_Mech_Sections) {
      mInternalStructure[section] = newVal;
   }
}

// Transform all vertices using m
void BipedMech::applyMatrix(const Matrix& m)
{
    RenderableObject::applyMatrix(m);

    for (size_t i = 0; i < Biped_Mech_MAX_PARTS; ++i) {
        m_joint[i] = m.Transform(m_joint[i]);
    }
}

// Return the number of the part corresponding to a section
// used by updateLoadoutGeometry() to find the mesh corresponding to the mech section
int BipedMech::section2part(int section, bool allowLowerArms)
{
	switch (section) {
		case Biped_Mech_Section_Head:
		case Biped_Mech_Section_Center_Torso:
		case Biped_Mech_Section_Left_Torso:
		case Biped_Mech_Section_Right_Torso:
				return Biped_Mech_Part_Torso;
		case Biped_Mech_Section_Left_Arm:
			if (allowLowerArms) {
				return Biped_Mech_Part_LeftLowerArm;
			} else {
				return Biped_Mech_Part_LeftArm;
			}
		case Biped_Mech_Section_Right_Arm:
			if (allowLowerArms) {
				return Biped_Mech_Part_RightLowerArm;
			} else {
				return Biped_Mech_Part_RightArm;
			}
		case Biped_Mech_Section_Left_Leg:
		case Biped_Mech_Section_Right_Leg:
		default:
			return Biped_Mech_Part_Torso;
	}
}

// Return the section corresponding to a limb
int BipedMech::limb2section(int limb)
{
	switch (limb) {
	case Biped_Mech_Part_Hip:
	case Biped_Mech_Part_Torso:
		return Biped_Mech_Section_Center_Torso; // could also be left/right torso ...
	case Biped_Mech_Part_LeftArm:
	case Biped_Mech_Part_LeftLowerArm:
		return Biped_Mech_Section_Left_Arm;
	case Biped_Mech_Part_RightArm:
	case Biped_Mech_Part_RightLowerArm:
		return Biped_Mech_Section_Right_Arm;
	case Biped_Mech_Part_LeftUpperLeg:
	case Biped_Mech_Part_LeftLowerLeg:
	case Biped_Mech_Part_LeftFoot:
		return Biped_Mech_Section_Left_Leg;
	case Biped_Mech_Part_RightUpperLeg:
	case Biped_Mech_Part_RightLowerLeg:
	case Biped_Mech_Part_RightFoot:
		return Biped_Mech_Section_Right_Leg;
	case Biped_Mech_Part_Windshield:
	case Biped_Mech_Part_Cockpit:
		return Biped_Mech_Section_Head;
	default:
		return -1; // unknown ...
	}
}

// Update mobility for a mech and send the "x disabled" event i mobility is reduced to zero
void BipedMech::setMobility(double newVal)
{
	if (newVal <= 0) {
		mMobility = 0.0;
		BGCPString eventName = getID() + BGCPString(" disabled");
		MechSim* sim = getMechSim();
		sim->updateObjectStatus(eventName, SubObjectiveType::event, false);
	} else {
		mMobility = newVal;
	}
}

// Mark all criticals in one section as destroyed and check for additional effects
void BipedMech::destroyLimb(int section)
{
	// Mark all criticals in that section as destroyed
	SET_SCOPE;
	for (int i = 0; i < getCriticalCount(section); ++i) {
		mCriticalDamaged[section][i] = true;
	}

	SET_OP;
	mArmorDamageFront[section] = mFrontArmor[section];
	mArmorDamageRear[section]  = mRearArmor[section];
	mInternalDamage[section]   = mInternalStructure[section];

	// If a side torso is destroyed, the corresponding arm is also destroyed
	// If a leg is destroyed, mobility drops to zero
	SET_OP;
	switch (section) {
	case Biped_Mech_Section_Head:
	case Biped_Mech_Section_Center_Torso:
		// If the head or center torso is destroyed, the whole mech is destroyed
		setStatus(destroyed);
		break;
	case Biped_Mech_Section_Left_Torso:
		destroyLimb(Biped_Mech_Section_Left_Arm);
		break;
	case Biped_Mech_Section_Right_Torso:
		destroyLimb(Biped_Mech_Section_Right_Arm);
		break;
	case Biped_Mech_Section_Left_Leg:
	case Biped_Mech_Section_Right_Leg:
		setMobility(0.0);
		break;
	}

	RETURN;
}

// Function to notify the Mech that it is taking damage
void BipedMech::receiveDamage(RenderableObject* limb, double damage, GameObject* sender, const Point3D& pointOfImpact)
{
	if (sender == this || damage <= 0.0) {
		return; // ignore self-inflicted damage ...
	}

	SET_SCOPE;
	if (mAI != nullptr && sender != nullptr) {
		mAI->setTarget(sender);
	}

	SET_OP;
	int limbHit = irand<int>(0, getObjectCount());
	size_t i = 0;
	while (i < getObjectCount()) {
		if (getObject(i) == limb) {
			limbHit = i;
			i = getObjectCount();
		} else {
			++i;
		}
	}

	bool damageFromFront = true;
	int section = limb2section(limbHit);

	if (section == Biped_Mech_Part_Torso) {
		// For the torso, find out if front/rear armor is hit
		Point3D position = getPosition();
		Matrix m = RotationMatrix(M_PI * torsoTwist / 180.0, 0, 1, 0);
		Point3D rotatedFront = m.Transform(p_front);
		Plane frontRearPlane(position, rotatedFront);
		auto halfSpaceTestResult = frontRearPlane.halfSpaceTest(pointOfImpact);
		if (halfSpaceTestResult < 0.0) {
			damageFromFront = false;
		}

		// Decide between left/right/center torso. As a rule of thumb, if the impact is more than a meter from the plane, a side torso was hit
		Point3D rotatedLeft = mM.Transform(p_left);
		Plane leftRightPlane(position, rotatedFront);
		halfSpaceTestResult = leftRightPlane.halfSpaceTest(pointOfImpact);
		if (halfSpaceTestResult > 1.0) {
			section = Biped_Mech_Section_Right_Torso;
		} else if (halfSpaceTestResult < -1.0) {
			section = Biped_Mech_Section_Left_Torso;
		} else {
			section = Biped_Mech_Section_Center_Torso;
		}
	}

	SET_OP;
	if (section >= 0 && section < (int) Biped_Mech_Sections) {
		if (damageFromFront) {
			SET_OP;
			auto armor = getArmor(section, Vehicle::Armor_Front);
			if (mArmorDamageFront[section] < armor) {
				if (mArmorDamageFront[section] + damage < armor) {
					// Armor completely absorbs the damage
					mArmorDamageFront[section] += damage;
					RETURN;
				} else {
					// Armor absorbs only part of the damage
					double damageDelta = armor - mArmorDamageFront[section];
					mArmorDamageFront[section] += damageDelta;
					damage -= damageDelta;
				}
			}
		} else {
			SET_OP;
			auto armor = getArmor(section, Vehicle::Armor_Rear);
			if (mArmorDamageRear[section] < armor) {
				if (mArmorDamageRear[section] + damage < armor) {
					// Armor completely absorbs the damage
					mArmorDamageRear[section] += damage;
					RETURN;
				} else {
					// Armor absorbs only part of the damage
					double damageDelta = armor - mArmorDamageRear[section];
					mArmorDamageRear[section] += damageDelta;
					damage -= damageDelta;
				}
			}
		}

		SET_OP;
		mInternalDamage[section] += damage;
		size_t sectionDamagedCriticals = std::count(mCriticalDamaged[section].begin(), mCriticalDamaged[section].end(), true);
		if (mInternalDamage[section] > mInternalStructure[section]) {
			// The limb is destroyed, all of its equipment becomes unusable, but this does not cause an internal ammunition explosion
			destroyLimb(section);
		} else {
			// Roll critical hits for the location
			// We cannot roll critical locations normally because of the delta damage increments ...
			// What we can do instead is keep the number of damaged criticals proportional to the damage caused. 
			// When all internal structure is destroyed, the last criticals are lost also. 
			auto sectionCriticals = getCriticalCount(section);
			double criticalsDamagedPercent = 100.0 * sectionDamagedCriticals / sectionCriticals;
			double internalStructureDamagePercent = 100.0 * mInternalDamage[section] / mInternalStructure[section];
			if (internalStructureDamagePercent > criticalsDamagedPercent && (int) sectionDamagedCriticals < sectionCriticals) {
				int criticalToDestroy;
				do {
					criticalToDestroy = irand(0, sectionCriticals);
				} while (mCriticalDamaged[section][criticalToDestroy] == true);
				checkCriticalDamage(section, criticalToDestroy);
			}
		}
	}

	RETURN;
}

// Check for for critical damage, e.g. if a weapon is destroyed by a critical hit
void BipedMech::checkCriticalDamage(int section, int criticalToDestroy)
{
	if (section >= Biped_Mech_Sections || criticalToDestroy >= (int) mCriticalDamaged[section].size()) {
		return;
	}

	SET_SCOPE;
	mCriticalDamaged[section][criticalToDestroy] = true;
	bool destroyed = false;
	WeaponType weaponType = getCriticals(section, criticalToDestroy);

	const MWEquipment* equipmentStats = getWeaponStats (weaponType);
	if (equipmentStats != nullptr) {
		COUT << "One critical of type " << equipmentStats->longName << " destroyed in section " << section << " on ";
		getName().printLn();
	}

	switch(weaponType) {
		case CE_Engine:
			// Count engine hits. The third engine hit disables the Mech until it can be repaired.
			++mEngineHits;
			destroyed = (mEngineHits > 2);
			break;
		case CE_Gyro:
			// Count gyro hits. The second hit makes it impossible for the mech to stand.
			++mGyroHits;
			if (mGyroHits > 1) {
				setMobility(0.0);
			}
			break;
		case CE_Life_Support:
			// todo: Any hit disables the system completely, resulting in damage to the pilot in case of heat or hostile environments
			mLifeSupportDestroyed = true;
			break;
		case CE_Cockpit:
			// A hit to the cockpit kills the pilot and disable this mech
			destroyed = true;
			break;
		case CE_Sensors:
			++mSensorDamage;
			break;
		case CE_Hip:
		case CE_Upper_Leg_Actuator:
		case CE_Lower_Leg_Actuator:
		case CE_Foot:
			// In contrast to the BT rules, in MechVM, any destroyed actuator reduces mobility by 10 %
			setMobility(mMobility-0.1);
			break;
		case CE_Jump_Jet:
			// Count jump jet hits
			++mJumpJetHits;
			break;
		case IS_Heat_Sink:
		case IS_Double_Heat_Sink:
		case Clan_Double_Heat_Sink:
			// Count damaged heat sink criticals
			// (this is a simplification compared to BT rules, where each critical hit can disable a heat sink completely)
			// (instead of figuring out what criticals form a DHS together)
			++mHeatSinkCriticalHits;
			break;
		default:
			// todo: a weapon was destroyed, mark it as unusable
			MountedWeapon* weapon = getMountedWeapon(section, criticalToDestroy);
			if (weapon != nullptr) {
				weapon->destroyed = true;
				// todo: check for exploded Gauss rifles ...
			}
			// todo: check for ammo explosion. Fireworks! Ammo location is already tracked.
			break;
	}

	if (destroyed) {
		setStatus(VehicleStatus::destroyed);
	}

	RETURN;
}

// Query damage to armor and internal structure
double BipedMech::getArmorDamage(int section, int armorType) const
{
	SET_SCOPE;
	if (section >= 0 && section < (int) mArmorDamageFront.size()) {
		if (armorType == Vehicle::Armor_Front) {
			RETURN mArmorDamageFront[section];
		} else {
			RETURN mArmorDamageRear[section];
		}
	} else {
		RETURN 0.0;
	}
}

double BipedMech::getInternalDamage(int section) const
{
	SET_SCOPE;
	if (section >= 0 && section < (int) mInternalDamage.size()) {
		RETURN mInternalDamage[section];
	} else {
		RETURN 0.0;
	}
}

// Check if the critical in section is damaged
bool BipedMech::criticalDamaged(int section, int critical) const
{
	SET_SCOPE;
	if (section >= 0
	&&  critical >= 0
	&&  section < (int) mCriticalDamaged.size()
	&&  critical < (int) mCriticalDamaged[section].size()) 
	{
		RETURN mCriticalDamaged[section][critical];
	} else {
		RETURN 0.0;
	}
}

// Get maximum speed in meters per second currently supported by the vehicle
double BipedMech::getMaxSpeed() const
{
	// One walking hex gives a speed of 30m in 10 seconds, or 3 meters per second
	return 3.0 * getDouble(Vehicle::Info_RunningHexes) * mMobility;
}

// Check if this vehicle differentiates between front and armor for a section
bool BipedMech::separateFrontAndRearArmor(int section) const
{
	if (section == Biped_Mech_Section_Center_Torso
	||  section == Biped_Mech_Section_Left_Torso
	||  section == Biped_Mech_Section_Right_Torso)
	{
		return true;
	} else {
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Check if a critical may be removed by the user

bool BipedMech::userMayRemoveDirectly (WeaponType weaponNo)
{
	auto alreadyPresent = countWeaponInAllSections(weaponNo);
	if (weaponNo == Unused_Critical_Slot
	||  weaponNo == CE_Engine
	||  weaponNo == CE_Gyro
	||  weaponNo == CE_Life_Support
	||  (weaponNo == CE_Cockpit && alreadyPresent < 2)
	||  weaponNo == CE_Sensors
	||  (weaponNo == CE_Shoulder && alreadyPresent <= 2)
	||  (weaponNo == CE_Upper_Arm_Actuator && alreadyPresent <= 2)
	||  (weaponNo == CE_Hip && alreadyPresent <= 2)
	||  (weaponNo == CE_Upper_Leg_Actuator && alreadyPresent <= 2)
	||  (weaponNo == CE_Lower_Leg_Actuator && alreadyPresent <= 2)
	||  (weaponNo == CE_Foot && alreadyPresent <= 2))
	{
		return false;
	} else {
		return true;
	}
}

// Show or hide the vehicle's windshield (cockpit view may require it to be hidden)
void BipedMech::windshieldVisible(bool newVal)
{
	SET_SCOPE;
	RenderableObject* child = getObject(Biped_Mech_Part_Windshield);
	size_t windshieldPolies = 0;
	if (child != nullptr) {
		if (newVal) {
			child->clearFlag(RenderableObject::Flag_invisible);
		} else {
			child->setFlag(RenderableObject::Flag_invisible);
		}
		windshieldPolies = child->getRecursivePolygonCount();
	}

	// If the mech does not have a windscreen, do not show the torso instead
	// todo: Mechs without a windshield are problematic because they can't take head hits ...
	if (windshieldPolies == 0) {
		child = getObject(Biped_Mech_Part_Torso);
		if (child != nullptr) {
			if (newVal) {
				child->clearFlag(RenderableObject::Flag_invisible);
			} else {
				child->setFlag(RenderableObject::Flag_invisible);
			}
		}
	}

	if (hideTorsoInCockpitView) {
		RenderableObject* torso = getObject(Biped_Mech_Part_Torso);
		if (torso != nullptr) {
			if (newVal) {
				torso->clearFlag(RenderableObject::Flag_invisible);
			} else {
				torso->setFlag(RenderableObject::Flag_invisible);
			}
		}
	}

	RETURN;
}

// Get tilt and turn for current aim direction
double BipedMech::getAimTilt() const
{
	return torsoTilt;
}

double BipedMech::getAimTurn() const
{
	return torsoTwist + armTwist;
}

// Move aim towards given angles by at most delta
void BipedMech::adjustAim(double desiredTorsoTwist, double desiredTorsoTilt, double delta)
{
	// Only one solution is possible, left or right. For Mechs like the Urbanmech, the torso won't turn beyond a certain limit,
	// so we need to find the right solution.
	// walkingDir: -90 for west, 90 for east, 0 for north
	// torsoTwist: -90 for right, 0 for straight, 90 for left 
	SET_SCOPE;
	double torsoTurnDelta = 0.0;

	// desiredTorsoTwist is given as the desired direction against north, including walking direction.
	// Change the coordinate system for currentTorsoDir to be relative to the walking direction instead
	desiredTorsoTwist = fixAngle(-180.0, desiredTorsoTwist, 180.0);
	if (torsoTwist - 1.0 > desiredTorsoTwist) {
		torsoTurnDelta = -1.0; // turn right
	} else if (torsoTwist + 1.0 < desiredTorsoTwist) {
		torsoTurnDelta = 1.0; // turn left
	}
	if (torsoTwist - 5.0 > desiredTorsoTwist) {
		torsoTurnDelta = -5.0; // turn right
	} else if (torsoTwist + 5.0 < desiredTorsoTwist) {
		torsoTurnDelta = 5.0; // turn left
	}

	// todo: if twisting the arm helps, we should ...

	torsoTurnDelta = putInRange(-1.0, torsoTurnDelta, 1.0);
	torsoTwist = putInRange(-torsoTwistLimit, torsoTwist + torsoTurnDelta, torsoTwistLimit);

	// Choose torso tilt
	// The mech wants to walk behind the enemy mech, but wants to aim right at it, so the angles differ
	if (torsoTilt + 1.0 < desiredTorsoTilt
	&&  torsoTilt + 1.0 < torsoTiltLimit)
	{
		torsoTilt += 1.0;
	}
	if (torsoTilt - 1.0 > desiredTorsoTilt
	&&  torsoTilt - 1.0 > -torsoTiltLimit)
	{
		torsoTilt -= 1.0;
	}

	RETURN;
}

void BipedMech::setAim(double turn, double tilt)
{
	torsoTilt = torsoTiltLimit * tilt / 30.0;
	torsoTwist = torsoTwistLimit * turn / 90.0;
	armTwist = armTwistLimit * turn / 90.0;
}

// Get max armor allowed for a section
int BipedMech::getMaxArmor(int section)
{
	SET_SCOPE;
	if (section != Biped_Mech_Section_Head) {
		RETURN 2 * getInternalStructure(section);
	} else {
		RETURN 9;
	}
}

// Get Mesh used for the cockpit
Mesh* BipedMech::getCockpitMesh()
{
	SET_SCOPE;
	Mesh* cockpitMesh = dynamic_cast<Mesh*>(getObject(Biped_Mech_Part_Cockpit));
	if (cockpitMesh == nullptr) {
		cockpitMesh = this;
	}
	RETURN cockpitMesh;
}

// Check if the vehicle's cockpit has a reticle that moves horizontally
bool BipedMech::reticleMovesHorizontally() const
{
	if (torsoTwistLimit < 5.0) {
		return true;
	} else {
		return false;
	}
}

// Check for a collision with a polygon and handle it
// New rules: 
// - for mechs, only feet collisions should lead to climbing, and only if the polygon is not too steep
// - the mech will loose speed and eventually fall if attempting to climb polygons that are too steep
// - instead of checking for a collision with a plane, check for collision with the actual polygon
// - figure out what part of the vehicle touches the collider
// - Use polygon's normal to decide on what side the vehicle should be
// - If a mesh collides with a polygon there are two polygons that collide (this cannot be decided precisely by looking only at vertices)
// - In case of collision: Check all vertices of the meshes if they lie on the wrong side of a polygon, and store a vector that would fix things
// - make sure the vehicle/mech does not move through walls

void BipedMech::checkCollision(const MeshPolygon* poly, Point3D& movement)
{
	if (poly == nullptr || poly->getVertexCount() < 3) {
		return;
	}

	SET_SCOPE;
	BoundingBox polyBB = poly->getTransformedBB();

	// Check what body parts collide and store a vector that would fix things for each body part
	bool touchesThisPoly = false;
	double moveUp = 0.0;
	if (polyBB.valid()) {
		for (size_t i = 0; i < Biped_Mech_MAX_PARTS; ++i) {
			RenderableObject* bodyPart = getObject(i);
			if (bodyPart != nullptr) {
				BoundingBox bodyPartBB = bodyPart->getTransformedBB();
				bodyPartBB.applyMovement(movement);
				if (bodyPartBB.valid()
				&& polyBB.intersects(bodyPartBB)) 
				{
					// Find the polygon(s) that cause the collision
					Mesh* bodyPartMesh = dynamic_cast<Mesh*>(bodyPart);
					for (size_t j = 0; j < bodyPartMesh->getPolygonCount(); ++j) {
						MeshPolygon* bodyPartPolygon = bodyPartMesh->getPolygon(j);
						if (poly->collides(bodyPartPolygon, movement)) {
							for (size_t k = 0; k < bodyPartPolygon->getVertexCount(); ++k) {
								int l = bodyPartPolygon->getVertexIndex(k);
								Point3D movedP = bodyPartMesh->getTransformedVertex(l) + movement;
								double moveUpHere = calcMoveUp(poly, movedP);
								if (moveUpHere > moveUp) {
									moveUp = moveUpHere;
									touchesThisPoly = true;
									mHasGroundContact = true;
								}
							}
						}
					}
				}
			}
		}
	}

	if (touchesThisPoly) {
		movement -= moveUp * poly->getTransformedNormal();
	}

	// We have a collision, which is caused by the vehicle moving towards an object. We need to eliminate the components
	// of the movement vector that cause the collision. So, transform the movement vector into the 3d space defined by the 
	// polygon's plane and normal, then remove any contributions rectangular to the plane. ed above, should result in exactly that.
	SET_OP;
	Point3D polyNormal = poly->transformedNormal;
	double polyAngle = Point3D::angle(Point3D(0, 1, 0), polyNormal);
	double angleBetweenPolyAndMovement = Point3D::angle(polyNormal, movement);
	/*if (polyAngle > 50 && angleBetweenPolyAndMovement > 90) {
		// todo: check, it doesn't work
		//illegalMove = true;
		movement = Point3D(0, 0, 0);
	}*/

	// Remove any components from the movement vector that are headed against the polygon normal
	// todo: this doesn't work. E.g., test it in mission pink.
	// Maybe M needs to be its own inverse?
	/*Plane plane;
	if (touchesThisPoly && poly->getPlane(plane)) {
		Point3D dir1, dir2;

		if ((plane.n-Point3D(0, 1, 0)).length() < 0.001) {
			dir1 = Point3D(1,0,0);
			dir2 = Point3D(0,0,1);
		} else if ((plane.n-Point3D(0, -1, 0)).length() < 0.001) {
			dir1 = Point3D(-1,0,0);
			dir2 = Point3D(0,0,-1);
		} else {
			dir1 = plane.n.crossMultiply(Point3D(0,1,0));
			dir2 = plane.n.crossMultiply(dir1);
		}

		double m[16] = {dir1.x, plane.n.x, dir2.x, 0.0,
			            dir1.y, plane.n.y, dir2.y, 0.0,
						dir1.z, plane.n.z, dir2.z, 0.0,
						0.0, 0.0, 0.0, 1.0};

		Matrix M(m);
		Point3D transformedMovement = M.Transform(movement);
		if (transformedMovement.y < 0.0) {
			transformedMovement.y *= -0.3;
			movement = transformedMovement.x * dir1 + transformedMovement.y * plane.n + transformedMovement.z * dir2;
		}
	}*/
	RETURN;
}

// Check if a section may mount jump jets
bool BipedMech::mayMountJJs(int section)
{
	if (section != Biped_Mech_Section_Head
	&&  section != Biped_Mech_Section_Left_Arm
	&&  section != Biped_Mech_Section_Right_Arm)
	{
		return true;
	} else {
		return false;
	}
}

// Set colors for wireframe rendering
void BipedMech::setWireframeColors()
{
	// Determine color for each location, translate and apply to the limbs ...
	SET_SCOPE;

	float sectionDamage[Biped_Mech_Sections];
	for (size_t section = 0; section < Biped_Mech_Sections; ++section) {
		auto damage = mArmorDamageFront[section] + mArmorDamageRear[section] + mInternalDamage[section];
		auto maxDamage = mFrontArmor[section] + mRearArmor[section];
		sectionDamage[section] = (float)(damage / maxDamage);
	}

	for (size_t i = 0; i < Biped_Mech_MAX_PARTS; ++i) {
		Mesh* mesh = getChild<Mesh>(i);
		if (mesh != nullptr) {
			int section = limb2section(i);
			if (section >= 0) {
				float d = sectionDamage[section];
				Color c = Color(d, 0, 1.0f-d);
				mesh->mWireframeColor = c;
			}
		}
	}

	RETURN;
}

// Based on the vehicle, connect the vehicle to a suitable AI
AbstractVehicleAI* BipedMech::createAI(bool isPlayer, int environment)
{
	if (isPlayer) {
		mAI = new PlayerGroundVehicle(this);
	} else {
		mAI = new GroundVehicleAI("GroundVehicleAI", this);
	}
	return mAI;
}

// Calculate heat for next frame
void BipedMech::calcNextFrameHeat(DWORD now, double deltaT)
{
	SET_SCOPE;
	double heatDelta = 0.0;

	// Add heat from walking/running
	if (hasGroundContact() && abs(getSpeed()) > 0.1) {
		double walkingHexes = getDouble(Vehicle::Info_WalkingHexes);
		if (getSpeed() > 3.0 * walkingHexes) {
			// Running causes 2 heat points in 10 seconds, or 0.2 per second
			heatDelta += 0.2 * deltaT;
		} else {
			// Walking causes 1 heat point in 10 seconds, or 0.1 per second
			heatDelta += 0.1 * deltaT;
		}
	}

	// Add heat from engine hits
	heatDelta += deltaT * 0.5 * getEngineHits();

	// todo: Add heat from jumping (min. 3, max is equal to number of hexes jumped)

	// Add heat from fired weapons
	size_t weaponsCount = getWeaponListEntryCount();
	for (size_t i = 0; i < weaponsCount; ++i) {
		double lastFired = getLastFireTime(i);
		const MWEquipment* baseWeapon = getBaseWeapon(i);
		if (lastFired > 0 && lastFired + 1000.0 * baseWeapon->fireDuration  > now) {
			heatDelta += deltaT * baseWeapon->Heat / baseWeapon->fireDuration;
		}
	}

	// Distract cooling from heat sinks
	// todo: factor in mHeatSinkCriticalHits
	int effectiveHeatSinks = getEffectiveHeatSinks();
	heatDelta -= 0.1 * effectiveHeatSinks * deltaT; // One heat sink gets rid of 1 point of heat per 10 seconds, so 0.1 points per second

	// todo: double effectiveness of heat sinks that are below water (max. of 6 additional heat points)
	
	mCurrentHeat = std::max(mCurrentHeat + heatDelta, 0.0);

	// todo: On high heat, the mech may shut down or ammo may explode
	// todo: high heat reduces speed
	// todo: MechWarrior may take damage if heat reaches or exceeds 15.0

	RETURN;
}

// Get current heat value for this Mech
double BipedMech::getHeat() const NOEXCEPT
{
	return mCurrentHeat;
}

// Calculate movement for next frame
void BipedMech::calcNextFrameMovement(double deltaT, RenderableObject* scene, DWORD now, double gravity)
{
	SET_SCOPE;
	if (isDestroyed()) {
		setSpeed(0.0);
		mAcceleration = Point3D();
		m_turnDelta = 0.0;
	}

	MechSim* sim = getMechSim();
	int env = sim->getEnvironment();
	if (gravity < 0.3 && env == space) {
		gravity = 0.3; // downthrust provided by jump jets (GBL zack, Mercs SPA1, SPA2)
	}

	// Update position
	bool movementChanged = false;
	Point3D oldPos = getPosition();
	Point3D thisFrameMovement = deltaT * mMovement;

	// Now check the new position against the scene and update the position, until all collisions have been resolved
	setHasGroundContact(false);
	int loops = 0;
	do {
		setMatrices(getSpeed(), now);
		Matrix m;
		cacheTransformedVertices(m);
		BoundingBox vehicleBB = getTransformedBB();
		vehicleBB.applyMovement(thisFrameMovement);

		// Mechs must not fall through the ground at zero no matter what
		if (vehicleBB.min().y < mLift) {
			oldPos.y += mLift - vehicleBB.min().y;
			setHasGroundContact(true);
			movementChanged = true;
			thisFrameMovement.y = 0.0;
			mMovement.y = 0.0;
		} else {
			movementChanged = false;
			std::vector<RenderableObject*> colliders;
			findColliders(vehicleBB, scene, this, colliders);
			for (size_t i = 0; i < colliders.size(); ++i) {
				RenderableObject* collider = colliders[i];
				if (collider != nullptr) {
					Vehicle* otherVehicle = ::getVehicle(collider);
					if (otherVehicle != nullptr) {
						resolveObjectCollision(this, otherVehicle);
					} else {
						resolveGroundCollision(this, collider, vehicleBB, thisFrameMovement, movementChanged);
					}
				}
			}
		}
		++loops;
	} while (movementChanged && loops < MAX_COLLISION_RESOLVE_LOOPS);

	if (loops > MAX_COLLISION_RESOLVE_LOOPS) {
		COUT << "Failed to resolve collisions for";
		MVMKFN.printLn();
		mMovement = Point3D(0, 0, 0);
	}

	// Now we can update the position
	setPosition(oldPos + thisFrameMovement);
	setMatrices(getSpeed(), now);
	cacheTransformedVertices(Matrix());

	if (mPosition.y < 0.1) {
		setHasGroundContact(true);
	}

	// A Mech's gyro can be used to rotate a mech even when the Mech does not have ground contact
	mHeading += m_turnDelta * deltaT;
	double degrees = M_PI * mHeading / 180.0;
	Matrix rot = RotationMatrix(degrees, 0.0, 1.0, 0.0);
	p_front = rot.Transform(Point3D(0, 0, 1));
	p_left = rot.Transform(Point3D(-1, 0, 0));

	int jjsActive = getActiveJumpJets();
	int jjs = getInteger(Vehicle::Info_JumpJets);
	if (jjsActive > 0) {
		if (mJumpJetFuel > 0.0) {
			double fuelForThisFrame = jjUseRate * jjsActive * deltaT;
			mJumpJetFuel -= fuelForThisFrame;
			mMovement += deltaT * mAcceleration;
		} else {
			deactivateJumpJets();
		}
	} else {
		mJumpJetFuel = std::min<double>(mJumpJetFuel + 0.5 * deltaT, jjs);
		mAcceleration.y -= gravity * deltaT;
		mMovement += deltaT * mAcceleration;

		if (hasGroundContact()) {
			if (abs(m_speedDelta) > 0.1) {
				setSpeed(getSpeed() + 40.0 * m_speedDelta * deltaT);
			}

			mMovement = getSpeed() * p_front; // mobility is already factored into m_speed
			if (mAcceleration.y < 0) {
				mAcceleration.y = 0.0;
				mMovement.y = 0.0;
			}
		}
	}

	RETURN;
}

DWORD BipedMech::getInternalStructure(int section) const
{
	return mInternalStructure[section];
}

double BipedMech::getWeight() const
{
	return realWeight;
}

// Get maximum weight mech chassis can carry
int BipedMech::getMaxWeight() const
{
	return tonnage;
}

// Check for items that the user may not add in the MechLab
bool BipedMech::userMayAddDirectly (int section, WeaponType weaponNo)
{
	SET_SCOPE;
	if (weaponNo == WeaponType::CE_Jump_Jet)
	{
		RETURN mayMountJJs(section);
	}

	SET_OP;
	auto alreadyPresent = countWeaponCriticals(section, weaponNo);
	if (weaponNo == WeaponType::Unused_Critical_Slot
	||  weaponNo == WeaponType::CE_Engine
	||  weaponNo == WeaponType::CE_Gyro
	||  weaponNo == WeaponType::CE_Life_Support
	||  weaponNo == WeaponType::CE_Cockpit
	||  weaponNo == WeaponType::CE_Sensors
	||  weaponNo == WeaponType::CE_Shoulder
	||  weaponNo == WeaponType::CE_Upper_Arm_Actuator
	|| (weaponNo == WeaponType::CE_Hip && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Upper_Leg_Actuator && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Lower_Leg_Actuator && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm)
	|| (weaponNo == WeaponType::CE_Foot && alreadyPresent > 0 && section > Biped_Mech_Section_Right_Arm))
	{
		RETURN false;
	}	else {
		RETURN true;
	}
}

// The point to attack may differ per vehicle class, the center of the volume isn't necessarily a good spot to attack
Point3D BipedMech::getAttackSpot(bool onGround) const
{
	SET_SCOPE;
	Point3D result = mPosition;
	if (!onGround) {
		// Normal attacks on a Mech focus on the torso center
		const RenderableObject* torsoGeometry = getObject(Biped_Mech_Part_Torso);
		if (torsoGeometry != nullptr) {
			auto torsoBB = torsoGeometry->getBoundingBox();
			if (torsoBB.valid()) {
				result = torsoBB.getCenter();
			}
		}
	} else {
		// Artillery weapons attack the ground below a Mech - that's where they do most damage
		const RenderableObject* geometry = getObject(Biped_Mech_Part_LeftFoot);
		if (geometry != nullptr) {
			auto bb = geometry->getBoundingBox();
			const Point3D& min = bb.min();
			const Point3D& max = bb.max();
			result = Point3D(0.5*(min.x + min.y), min.y, 0.5*(min.z+max.z));
		}
	}
	RETURN result;
}

// Get sensor damage - 0 equals no damage, 1 equals sensors completely destroyed
double BipedMech::getSensorDamage()
{
	SET_SCOPE;
	double result = putInRange(0.0, mSensorDamage / 2.0, 1.0);
	RETURN result;
}

