Ejemplo n.º 1
0
void ParticleRenderer::CreateExplosion(const vec3& position, float strength, const vec3& velocity, float falloff, float time, const vec3& start_fire_color, const vec3& fire_color,
	const vec3& start_smoke_color, const vec3& smoke_color, const vec3& sharpnel_color, int fires, int smokes, int sparks, int shrapnels) {
	ScopeLock lock(lock_);

	const float random_xy_end_speed = 1.0f;
	const float strength2 = (strength>1)? ::sqrt(strength) : strength*strength;
	const float particle_size = strength2*0.5f;
	const float speed = velocity.GetLength() * 0.01f;
	CreateBillboards(position,  7*strength2+ 1*speed,      velocity, Math::Lerp(velocity,gravity_*-0.2f,falloff), random_xy_end_speed, 5.3f/time, particle_size,      start_fire_color, fire_color, fires_, fires);
	CreateBillboards(position,  8*strength2+ 1*speed,      velocity, Math::Lerp(velocity,gravity_*+0.2f,falloff), random_xy_end_speed,    3/time, particle_size*2,    start_smoke_color, smoke_color, smokes_, smokes);
	CreateBillboards(position, 20*strength2+20*speed, 1.2f*velocity, Math::Lerp(velocity,gravity_*+0.8f,falloff), random_xy_end_speed, 4.5f/time, particle_size*0.4f, vec3(), vec3(), sparks_, sparks);
	CreateBillboards(position,  9*strength2+10*speed, 1.1f*velocity, Math::Lerp(velocity,gravity_*+1.1f,falloff), random_xy_end_speed, 1.1f/time, particle_size*0.5f, sharpnel_color, sharpnel_color, shrapnels_, shrapnels);

	const float min_spark_velocity2 = strength2*100;
	const vec3 cam_plane = renderer_->GetCameraTransformation().GetOrientation() * vec3(0,1,0);
	BillboardArray::reverse_iterator x = sparks_.rbegin();
	for (int y = 0; y < sparks; ++y, ++x) {
		x->velocity_ = x->velocity_.ProjectOntoPlane(cam_plane);
		float speed2 = x->velocity_.GetLengthSquared();
		if (speed2 < min_spark_velocity2) {
			x->velocity_.Mul(::sqrt(min_spark_velocity2/speed2));
		}
	}

	if (fires > 0) {
		const Billboard& fire = fires_.back();
		CreateTempLight(fire_color, strength, fire.position_, fire.velocity_, fire.target_velocity_, fire.time_factor_);
	}
}
Ejemplo n.º 2
0
void Game::OnCollision(const vec3& force, const vec3& torque, const vec3& position,
	cure::ContextObject* object1, cure::ContextObject* object2,
	tbc::PhysicsManager::BodyID body1_id, tbc::PhysicsManager::BodyID body2_id) {
	(void)body2_id;
	collision_sound_manager_->OnCollision(force, torque, position, object1, object2, body1_id, 2000, false);

	const float _force = force.GetLength();
	if (object1 == ball_ && _force > 1.0f) {
		score_ += ::sqrt(_force) * 34;
	}
}
Ejemplo n.º 3
0
void ServerMine::OnForceApplied(cure::ContextObject* other_object,
	tbc::PhysicsManager::BodyID own_body_id, tbc::PhysicsManager::BodyID other_body_id,
	const vec3& force, const vec3& torque,
	const vec3& position, const vec3& relative_velocity) {
	(void)other_object;
	(void)own_body_id;
	(void)other_body_id;
	(void)torque;
	(void)position;
	(void)relative_velocity;

	float _force = force.GetLength()/GetMass() * 0.002f;
	if (ticks_til_fully_activated_ == 0 ||
		(other_object->GetPhysics()->GetPhysicsType() == tbc::ChunkyPhysics::kDynamic &&
		!dynamic_cast<ServerMine*>(other_object))) {
		_force *= 10;
	}
	if (_force > 1) {
		cure::Health::Add(this, _force * -0.045f, true);
	}
}
Ejemplo n.º 4
0
bool Game::MoveRacket() {
	if (GetRacket() && GetRacket()->IsLoaded() &&
		GetBall() && GetBall()->IsLoaded()) {
		xform racket_transform;
		GameTicker::GetPhysicsManager(IsThreadSafe())->GetBodyTransform(
			GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			racket_transform);
		vec3 racket_linear_velocity;
		GameTicker::GetPhysicsManager(IsThreadSafe())->GetBodyVelocity(
			GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			racket_linear_velocity);
		vec3 racket_angular_velocity;
		GameTicker::GetPhysicsManager(IsThreadSafe())->GetBodyAngularVelocity(
			GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			racket_angular_velocity);

		// Calculate where ball will be as it passes z = racket z.
		vec3 ball_position =
			GameTicker::GetPhysicsManager(IsThreadSafe())->GetBodyPosition(GetBall()->GetPhysics()->GetBoneGeometry(0)->GetBodyId());
		vec3 ball_velocity;
		GameTicker::GetPhysicsManager(IsThreadSafe())->GetBodyVelocity(
			GetBall()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			ball_velocity);
		if (ball_position.z < -2) {
			ball_position.Set(0, 0, 0.4f);
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyTransform(GetBall()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), xform(quat(), ball_position));
			ball_velocity.Set(0, 0, 2.0f);
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyVelocity(GetBall()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), ball_velocity);
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyAngularVelocity(GetBall()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), vec3());
			racket_transform.SetIdentity();
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyTransform(GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), racket_transform);
			racket_linear_velocity.Set(0, 0, 0);
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyVelocity(GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), racket_linear_velocity);
			racket_angular_velocity.Set(0, 0, 0);
			GameTicker::GetPhysicsManager(IsThreadSafe())->SetBodyAngularVelocity(GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(), racket_angular_velocity);
			return false;
		}
		vec3 home;
		const float h = ball_position.z - racket_transform.GetPosition().z;
		if (h > -0.5f) {
			home.Set(ball_position.x*0.8f, ball_position.y*0.8f, 0);
		}
		const float vup = ball_velocity.z;
		const float a = +9.82f / 2;
		const float b = -vup;
		const float c = +h;
		const float b2 = b*b;
		const float _4ac = 4*a*c;
		if (b2 < _4ac || _4ac < 0) {
			// Does not compute.
		} else {
			const float t = (-b + sqrt(b2 - _4ac)) / (2*a);
			if (t > 0) {
				home.x += ball_velocity.x * t;
				home.y += ball_velocity.y * t;
			}
		}
		// Set linear force.
		const vec3 direction_home = home - racket_transform.GetPosition();
		float f = direction_home.GetLength();
		f *= 50;
		f *= f;
		vec3 _force = direction_home * f;
		_force -= racket_linear_velocity * 10;
		float user_force_factor = ::fabs(racket_lift_factor_) * 1.7f;
		user_force_factor = std::min(1.0f, user_force_factor);
		const float racket_acceleration = 100.0f * racket_lift_factor_;
		const float zForce = Math::Lerp(_force.z, racket_acceleration, user_force_factor);
		_force.z = zForce;
		f = _force.GetLength();
		if (f > 100) {
			_force *= 100 / f;
		}
		//mLog.Infof("force = (%f, %f, %f"), lForce.x, lForce.y, lForce.z);
		GameTicker::GetPhysicsManager(IsThreadSafe())->AddForce(
			GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			_force);

		// Set torque. Note that racket is "flat" along the XY-plane.
		//const float lTiltAngleFactor = 1.2f;
		//const float dx = direction_home.x * lTiltAngleFactor;
		//const float dy = direction_home.y * lTiltAngleFactor;
		//const float dx = -racket_transform.GetPosition().x * lTiltAngleFactor;
		//const float dy = -racket_transform.GetPosition().y * lTiltAngleFactor;
		racket_down_direction_.Normalize();
		const vec3 home_torque = vec3(::acos(racket_down_direction_.y), ::acos(racket_down_direction_.x), 0);
		vec3 racket_torque = racket_transform.GetOrientation() * vec3(0,0,1);
		racket_torque = vec3(::acos(racket_torque.y), ::acos(racket_torque.x), 0);
		vec3 angle_home = home_torque - racket_torque;
		angle_home.y = -angle_home.y;
		angle_home.z = 0;
		f = Math::Clamp(-ball_velocity.z, 0.0f, 4.0f) / 4.0f;
		f = Math::Lerp(0.8f, 0.3f, f);
		f = angle_home.GetLength() * f;
		f *= f;
		f = 1;
		vec3 _torque = angle_home * f;
		_torque -= racket_angular_velocity * 0.2f;
		//mLog.Infof("torque = (%f, %f, %f"), lTorque.x, lTorque.y, lTorque.z);
		GameTicker::GetPhysicsManager(IsThreadSafe())->AddTorque(
			GetRacket()->GetPhysics()->GetBoneGeometry(0)->GetBodyId(),
			_torque);
	}
	return true;
}
Ejemplo n.º 5
0
void JetEngineEmitter::EmitFromTag(const CppContextObject* object, const uitbc::ChunkyClass::Tag& tag, float frame_time) {
	bool particles_enabled;
	v_get(particles_enabled, =, UiCure::GetSettings(), kRtvarUi3DEnableparticles, false);
	if (!particles_enabled) {
		return;
	}

	enum FloatValue {
		kFvStartR = 0,
		kFvStartG,
		kFvStartB,
		kFvEndR,
		kFvEndG,
		kFvEndB,
		kFvX,
		kFvY,
		kFvZ,
		kFvRadiusX,
		kFvRadiusY,
		kFvRadiusZ,
		kFvScaleX,
		kFvScaleY,
		kFvScaleZ,
		kFvDirectionX,
		kFvDirectionY,
		kFvDirectionZ,
		kFvDensity,
		kFvOpacity,
		kFvOvershootOpacity,
		kFvOvershootCutoffDot,
		kFvOvershootDistanceUpscale,
		kFvOvershootEngineFactorBase,
		kFvCount
	};
	if (tag.float_value_list_.size() != kFvCount ||
		tag.string_value_list_.size() != 0 ||
		tag.body_index_list_.size() != 0 ||
		tag.engine_index_list_.size() != 1 ||
		tag.mesh_index_list_.size() < 1) {
		log_.Errorf("The fire tag '%s' has the wrong # of parameters.", tag.tag_name_.c_str());
		deb_assert(false);
		return;
	}
	const int engine_index = tag.engine_index_list_[0];
	if (engine_index >= object->GetPhysics()->GetEngineCount()) {
		return;
	}
	const tbc::PhysicsEngine* engine = object->GetPhysics()->GetEngine(engine_index);
	const float throttle_up_speed = Math::GetIterateLerpTime(tag.float_value_list_[kFvOvershootEngineFactorBase]*0.5f, frame_time);
	const float throttle_down_speed = Math::GetIterateLerpTime(tag.float_value_list_[kFvOvershootEngineFactorBase], frame_time);
	const float engine_throttle = engine->GetLerpThrottle(throttle_up_speed, throttle_down_speed, true);
	const quat orientation = object->GetOrientation();
	vec3 _radius(tag.float_value_list_[kFvRadiusX], tag.float_value_list_[kFvRadiusY], tag.float_value_list_[kFvRadiusZ]);
	_radius.x *= Math::Lerp(1.0f, tag.float_value_list_[kFvScaleX], engine_throttle);
	_radius.y *= Math::Lerp(1.0f, tag.float_value_list_[kFvScaleY], engine_throttle);
	_radius.z *= Math::Lerp(1.0f, tag.float_value_list_[kFvScaleZ], engine_throttle);
	vec3 _position(tag.float_value_list_[kFvX], tag.float_value_list_[kFvY], tag.float_value_list_[kFvZ]);
	_position = orientation * _position;
	const vec3 _color(tag.float_value_list_[kFvEndR], tag.float_value_list_[kFvEndB], tag.float_value_list_[kFvEndB]);

	bool create_particle = false;
	const float density = tag.float_value_list_[kFvDensity];
	float exhaust_intensity;
	v_get(exhaust_intensity, =(float), UiCure::GetSettings(), kRtvarUi3DExhaustintensity, 1.0);
	interleave_timeout_ -= Math::Lerp(0.3f, 1.0f, engine_throttle) * exhaust_intensity * frame_time;
	if (interleave_timeout_ <= 0) {	// Release particle this frame?
		create_particle = true;
		interleave_timeout_ = 0.05f / density;
	} else {
		create_particle = false;
	}

	const float dx = tag.float_value_list_[kFvRadiusX];
	const float dy = tag.float_value_list_[kFvRadiusY];
	const float dz = tag.float_value_list_[kFvRadiusZ];
	const vec3 start_color(tag.float_value_list_[kFvStartR], tag.float_value_list_[kFvStartB], tag.float_value_list_[kFvStartB]);
	const float _opacity = tag.float_value_list_[kFvOpacity];
	const vec3 direction = orientation * vec3(tag.float_value_list_[kFvDirectionX], tag.float_value_list_[kFvDirectionY], tag.float_value_list_[kFvDirectionZ]);
	const vec3 velocity = direction + object->GetVelocity();
	uitbc::ParticleRenderer* particle_renderer = (uitbc::ParticleRenderer*)ui_manager_->GetRenderer()->GetDynamicRenderer("particle");
	const float particle_time = density;
	float particle_size;	// Pick second size.
	if (dx > dy && dy > dz) {
		particle_size = dy;
	} else if (dy > dx && dx > dz) {
		particle_size = dx;
	} else {
		particle_size = dz;
	}
	particle_size *= 0.2f;

	const float _distance_scale_factor = tag.float_value_list_[kFvOvershootDistanceUpscale];
	for (size_t y = 0; y < tag.mesh_index_list_.size(); ++y) {
		tbc::GeometryBase* mesh = object->GetMesh(tag.mesh_index_list_[y]);
		if (mesh) {
			int phys_index = -1;
			str mesh_name;
			xform transform;
			float mesh_scale;
			((uitbc::ChunkyClass*)object->GetClass())->GetMesh(tag.mesh_index_list_[y], phys_index, mesh_name, transform, mesh_scale);
			transform = mesh->GetBaseTransformation() * transform;
			vec3 mesh_pos = transform.GetPosition() + _position;

			const vec3 cam_distance = mesh_pos - ui_manager_->GetRenderer()->GetCameraTransformation().GetPosition();
			const float distance = cam_distance.GetLength();
			const vec3 cam_direction(cam_distance / distance);
			float overshoot_factor = -(cam_direction*direction);
			if (overshoot_factor > tag.float_value_list_[kFvOvershootCutoffDot]) {
				overshoot_factor = Math::Lerp(overshoot_factor*0.5f, overshoot_factor, engine_throttle);
				const float opacity = (overshoot_factor+0.6f) * tag.float_value_list_[kFvOvershootOpacity];
				DrawOvershoot(mesh_pos, _distance_scale_factor*distance, _radius, _color, opacity, cam_direction);
			}

			if (create_particle) {
				const float sx = Random::Normal(0.0f, dx*0.5f, -dx, +dx);
				const float sy = Random::Normal(0.0f, dy*0.5f, -dy, +dy);
				const float sz = Random::Normal(0.0f, dz*0.5f, -dz, +dz);
				mesh_pos += orientation * vec3(sx, sy, sz);
				particle_renderer->CreateGlow(particle_time, particle_size, start_color, _color, _opacity, mesh_pos, velocity);
			}
		}
	}
}
Ejemplo n.º 6
0
void VehicleAi::OnTick() {
	if (!game_->GetVehicle() || !game_->GetVehicle()->IsLoaded() ||
		!game_->GetLevel() || !game_->GetLevel()->IsLoaded() ||
		game_->GetFlybyMode() != Game::kFlybyInactive) {
		return;
	}

	const cure::TimeManager* _time = GetManager()->GetGameManager()->GetTimeManager();
	const int mode_run_delta_frame_count = _time->GetCurrentPhysicsFrameDelta(mode_start_frame_);
	const float mode_run_time = _time->ConvertPhysicsFramesToSeconds(mode_run_delta_frame_count);
	const float aim_distance = AIM_DISTANCE;

	float strength = 1.0f;
	const vec3 _position = game_->GetVehicle()->GetPosition();
	const vec3 _velocity = game_->GetVehicle()->GetVelocity();
	switch (mode_) {
		case kModeFindBestPath:
		case kModeFindPathOffElevator: {
			float start_time = 0.5f;
			if (active_path_ != -1) {
				// Synchronize all paths.
				Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
				start_time = _path->GetCurrentInterpolationTime();
				active_path_ = -1;
			}
			log_.Headlinef("Trying to find new path... starting iterating from  %.2f.", start_time);
			vec3 elevator_direction;
			if (mode_ == kModeFindPathOffElevator) {
				game_->GetVehicle()->SetEnginePower(0, 0);
				game_->GetVehicle()->SetEnginePower(2, -strength);	// Negative = use full brakes, not only hand brake.
				const cure::Elevator* _nearest_elevator;
				const vec3 elevator_position = GetClosestElevatorPosition(_position, _nearest_elevator);
				if (elevator_position.GetDistanceSquared(_position) > ELEVATOR_TOO_CLOSE_DISTANCE*ELEVATOR_TOO_CLOSE_DISTANCE) {
					log_.AHeadline("Fell off elevator while looking for get-off route. Looking for somewhere else to go.");
					SetMode(kModeFindBestPath);
					return;
				}
				elevator_direction = _nearest_elevator->GetVelocity().GetNormalized(0.5f);
			}
			float best_path_distance = 1000000;
			std::vector<PathIndexLikeliness> relevant_paths;
			bool lifting_towards_goal = false;
			const int path_count = game_->GetLevel()->QueryPath()->GetPathCount();
			for (int x = 0; x < path_count; ++x) {
				bool current_lifting_towards_goal = false;
				Spline* _path = game_->GetLevel()->QueryPath()->GetPath(x);
				_path->GotoAbsoluteTime(start_time);
				float _likeliness = 1;
				const float nearest_distance = GetClosestPathDistance(_position, x, &_likeliness)/SCALE_FACTOR/2;
				log_.Infof(" - Path %2i is %2.2f units away.", x, nearest_distance);
				if (mode_ == kModeFindPathOffElevator) {
					if (_path->GetCurrentInterpolationTime() > 0.7f) {
						// This path is probably the one I used to get ON the elevator (or one
						// just like it from another direction), we're not using that!
						log_.AInfo("   (Not relevant, too close to path end.)");
						continue;
					} else {
						const float towards_distance = GetClosestPathDistance(_position+elevator_direction, x)/SCALE_FACTOR/2;
						if (towards_distance < nearest_distance) {
							current_lifting_towards_goal = true;
							if (!lifting_towards_goal) {
								lifting_towards_goal = true;
								best_path_distance = 1000000;
							}
						}
					}
				}
				if (!current_lifting_towards_goal && lifting_towards_goal) {
					// This elevator isn't heading in the right direction, but at least one other is.
					continue;
				}
				PathIndexLikeliness pl;
				pl.path_index_ = x;
				pl.likeliness_ = _likeliness;
				pl.distance_ = nearest_distance;
				relevant_paths.push_back(pl);
				if (nearest_distance < best_path_distance) {
					best_path_distance = nearest_distance;
				}
			}
			// Sort out those that are too far away.
			float total_likeliness = 0;
			std::vector<PathIndexLikeliness>::iterator x;
			for (x = relevant_paths.begin(); x != relevant_paths.end();) {
				if (x->distance_ < best_path_distance+2.0f) {
					total_likeliness += x->likeliness_;
					++x;
				} else {
					x = relevant_paths.erase(x);
				}
			}
			if (mode_ == kModeFindPathOffElevator) {
				if (best_path_distance > 5 || relevant_paths.size() != 1) {
					if (relevant_paths.size() == 1) {
						// Point wheels in the right direction for us to get off safely.
						Spline* _path = game_->GetLevel()->QueryPath()->GetPath(relevant_paths[0].path_index_);
						const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
						const vec3 wanted_direction = _path->GetValue() - _position;
						const float angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
						game_->GetVehicle()->SetEnginePower(1, angle*0.5f);
					}
					log_.Headlinef("On elevator: too long distance to path %.1f, or too many paths %u.", best_path_distance, relevant_paths.size());
					if (best_path_distance > 15) {
						const cure::Elevator* _nearest_elevator;
						const vec3 nearest_lift_position = GetClosestElevatorPosition(_position, _nearest_elevator);
						if (nearest_lift_position.GetDistanceSquared(_position) > ELEVATOR_TOO_CLOSE_DISTANCE*ELEVATOR_TOO_CLOSE_DISTANCE) {
							// DUCK!!! We fell off!
							log_.AHeadline("Was on elevator: I'm far from the elevator, so must've fallen off!");
							SetMode(kModeFindBestPath);
						}
					}
					if (mode_run_time >= 20) {
						log_.AHeadline("On elevator: been here too long, getting off!");
						SetMode(kModeFindBestPath);
					}
					return;
				}
				log_.Headlinef("Getting off elevator: distance to path %.1f.", best_path_distance);
			}
			deb_assert(!relevant_paths.empty());
			if (relevant_paths.empty()) {
				return;
			}
			const float picked_likeliness = Random::Uniform(0.0f, total_likeliness);
			total_likeliness = 0;
			for (x = relevant_paths.begin(); x != relevant_paths.end(); ++x) {
				const float next_likeliness = total_likeliness + x->likeliness_;
				if (picked_likeliness >= total_likeliness && picked_likeliness <= next_likeliness) {
					active_path_ = x->path_index_;
					break;
				}
				total_likeliness = next_likeliness;
			}
			if (active_path_ < 0) {
				active_path_ = relevant_paths[Random::GetRandomNumber() % relevant_paths.size()].path_index_;
			}
			Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
			const float wanted_distance = aim_distance;
			float step = wanted_distance * _path->GetDistanceNormal();
			if (step + _path->GetCurrentInterpolationTime() > 1) {
				step = 1 - _path->GetCurrentInterpolationTime();
			}
			_path->StepInterpolation(step);
			// Fetch ending position.
			const float t = _path->GetCurrentInterpolationTime();
			_path->GotoAbsoluteTime(END_PATH_TIME);
			elevator_get_on_position_ = _path->GetValue();
			_path->GotoAbsoluteTime(t);

			log_.Headlinef("Picked path %i (%i pickable."), active_path_, relevant_paths.size());
			if (mode_ == kModeFindPathOffElevator) {
				SetMode(kModeGetOffElevator);
			} else {
				SetMode(kModeHeadingBackOnTrack);
			}
		} break;
		case kModeHeadingBackOnTrack: {
			if (mode_run_delta_frame_count%5 == 2) {
				const float velocity_scale_factor = std::min(1.0f, _velocity.GetLength() / 2.5f);
				Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
				const float current_time = _path->GetCurrentInterpolationTime();
				const float nearest_path_distance = GetClosestPathDistance(_position, active_path_, 0, 1);
				if (nearest_path_distance > 3.0f) {
					// First verify that we haven't ended up under path somehow. We do that by checking
					// steepness, since pure Z-distance may be big when going over ditches.
					const vec3 path_position = _path->GetValue();
					const float steepness = (path_position.z - _position.z) / nearest_path_distance;
					//log_.Infof("Checking steepness, nearest path distance is %.3f, steepness is %.3f.", nearest_path_distance, steepness);
					if (steepness > 0.6f) {
						log_.Infof("Searching for new, better path, we seem to have ended up under the path. Beneath a bridge perhaps? Nearest path is %.2f, steepness is %.2f.", nearest_path_distance, steepness);
						SetMode(kModeFindBestPath);
						return;
					}
				}
				_path->GotoAbsoluteTime(current_time);
				if (nearest_path_distance < SCALE_FACTOR * OFF_COURSE_DISTANCE * velocity_scale_factor) {
					// We were able to return to normal, keep on running.
					SetMode(kModeNormal);
					return;
				}
				/*else if (nearest_path_distance > SCALE_FACTOR * OFF_COURSE_DISTANCE * velocity_scale_factor * 5) {
					// We're far off, perhaps we fell down from a plateu.
					active_path_ = -1;
					SetMode(kModeFindBestPath);
					return;
				}*/
				else if (mode_run_time > 7.0f) {
					SetMode(kModeFindBestPath);
					return;
				}
			}
		}
		// TRICKY: fall through.
		case kModeNormal:
		case kModeGetOnElevator:
		case kModeGetOffElevator: {
			if (mode_ == kModeGetOnElevator) {
				if (mode_run_time > 4.5) {
					log_.Headlinef("Something presumably hinders me getting on the elevator, back square one. (mode run time=%f"), mode_run_time);
					SetMode(kModeFindBestPath);
					return;
				}
				const cure::Elevator* _nearest_elevator;
				const vec3 nearest_lift_position = GetClosestElevatorPosition(elevator_get_on_position_, _nearest_elevator);
				if (nearest_lift_position.z > _position.z+0.5f) {
					log_.AHeadline("Couldn't get on in time, going back to waiting.");
					SetMode(kModeWaitingForElevator);
					return;
				}
			}

			if (mode_ != kModeHeadingBackOnTrack && mode_ != kModeGetOnElevator && mode_run_delta_frame_count%20 == 19) {
				const float _distance = GetClosestPathDistance(_position);
				if (_distance > SCALE_FACTOR * TOTALLY_OFF_COURSE_DISTANCE) {
					log_.AHeadline("Fell off something. Trying some new path.");
					SetMode(kModeFindBestPath);
					return;
				}
				const float velocity_scale_factor = ((mode_ == kModeNormal)? 1.0f : 3.0f) * Math::Clamp(_velocity.GetLength() / 2.5f, 0.3f, 1.0f);
				if (_distance > SCALE_FACTOR * OFF_COURSE_DISTANCE * velocity_scale_factor) {
					log_.AHeadline("Going about my way, but got offside somehow. Heading back.");
					SetMode(kModeHeadingBackOnTrack);
					return;
				}
			}

			Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
			vec3 target = _path->GetValue();

			// Check if vehicle stopped. That would mean either crashed against something or too steep hill.
			if (mode_run_delta_frame_count%7 == 4 && game_->GetVehicle()->GetHealth() > 0) {
				if (QueryVehicleHindered(_time, _velocity)) {
					const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
					const vec3 wanted_direction = target-_position;
					const float forward_angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
					// Amplify angle to be either full left or full right.
					const float angle = (forward_angle < 0)? -1.0f : 1.0f;
					game_->GetVehicle()->SetEnginePower(1, -angle);
					SetMode(kModeBackingUp);
					return;
				}
			}

			// Are we heading towards an elevator?
			if (mode_ != kModeGetOnElevator && mode_ != kModeGetOffElevator && _path->GetType() == "to_elevator") {
				if (_path->GetDistanceLeft() <= ELEVATOR_WAIT_DISTANCE) {
					if (elevator_get_on_position_.GetDistanceSquared(_position) <= ELEVATOR_WAIT_DISTANCE*ELEVATOR_WAIT_DISTANCE) {
						log_.AHeadline("Normal mode close to end of path to elevator, changing mode.");
						SetMode(kModeWaitingForElevator);
						return;
					}
				}
			}

			// Did we just pass (fly by?) the goal?
			if (mode_run_delta_frame_count%3 == 0) {
				const vec3 goal_direction = game_->GetGoal()->GetPosition() - _position;
				if (::fabs(goal_direction.z) < 2 &&
					goal_direction.GetLengthSquared() < ELEVATOR_FAR_DISTANCE*ELEVATOR_FAR_DISTANCE &&
					_velocity.GetLengthSquared() < 6*6) {
					const vec3 vehicle_direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
					const float delta_angle = ::fabs(LEPRA_XY_ANGLE(goal_direction, vehicle_direction));
					if (delta_angle >= PIF-PIF/4 && delta_angle <= PIF+PIF/4) {
						log_.AHeadline("Passed goal, it's right behind me!");
						SetMode(kModeBackingUpToGoal);
						return;
					}
				}
			}

			// Step target (aim) ahead.
			{
				const float actual_distance2 = target.GetDistanceSquared(_position);
				const float max_aim_factor = (mode_ == kModeGetOffElevator)? 1.0f : 1.5f;
				const float wanted_distance = aim_distance * Math::Clamp(_velocity.GetLength() / 2.5f, 0.5f, max_aim_factor);
				if (actual_distance2 < wanted_distance*wanted_distance) {
					const float move_ahead = wanted_distance*1.1f - ::sqrt(actual_distance2);
					_path->StepInterpolation(move_ahead * _path->GetDistanceNormal());
					log_volatile(log_.Debugf("Stepping %f (=%f m from %f."), move_ahead*_path->GetDistanceNormal(), move_ahead, _path->GetCurrentInterpolationTime()));
				}

				// Check if we're there yet.
				const float t = _path->GetCurrentInterpolationTime();
				_path->GotoAbsoluteTime(1.0f);
				const float target_distance = (mode_ == kModeGetOnElevator)? ON_ELEVATOR_DISTANCE : ON_GOAL_DISTANCE + game_->GetVehicle()->GetForwardSpeed()/4;
				if (IsCloseToTarget(_position, target_distance)) {
					const bool towards_elevator = (_path->GetType() == "to_elevator");
					if (towards_elevator) {
						if (mode_ == kModeGetOnElevator) {
							SetMode(kModeOnElevator);
							return;
						} else if (mode_ != kModeGetOffElevator) {
							// We got off track somewhere, try to shape up!
							log_.AHeadline("Normal mode target wrapped on our way to an elevator, changing mode.");
							SetMode(kModeWaitingForElevator);
							return;
						}
					} else {
						SetMode(kModeStoppingAtGoal);
						return;
					}
				}
				_path->GotoAbsoluteTime(t);

				target = _path->GetValue();
			}

			const float get_off_delay_time = 0.4f;
			if (!(mode_ == kModeGetOffElevator && mode_run_time < get_off_delay_time)) {
				// Move forward.
				game_->GetVehicle()->SetEnginePower(0, +strength);
				game_->GetVehicle()->SetEnginePower(2, 0);
			}

			// Steer.
			const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
			const vec3 wanted_direction = target-_position;
			float angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
			if (mode_ == kModeGetOffElevator) {
				// Aborting too early might cause us to stop, waiting for the next ride in mid-air.
				const float get_off_distance = GetClosestElevatorRadius() + ELEVATOR_GOT_OFF_EXTRA_DISTANCE;
				vec2 elevator_get_off2d(elevator_get_off_position_.x, elevator_get_off_position_.y);
				vec2 position2d(_position.x, _position.y);
				log_.Infof("ElevatorGetOff (%f;%f, pos (%f;%f)"), elevator_get_off2d.x, elevator_get_off2d.y, position2d.x, position2d.y);
				if (elevator_get_off2d.GetDistanceSquared(position2d) > get_off_distance*get_off_distance) {
					SetMode(kModeNormal);
				}
				angle *= 2;	// Make steering more powerful while getting off.
			}
			game_->GetVehicle()->SetEnginePower(1, +angle);
			last_average_angle_ = Math::Lerp(last_average_angle_, angle, 0.5f);

			// Check if we need to slow down.
			const float high_speed = SCALE_FACTOR * 2.7f;
			const float abs_angle = ::fabs(angle);
			if (_velocity.GetLengthSquared() > high_speed*high_speed) {
				if (abs_angle > 0.2f) {
					float factor = 0.10f;
					game_->GetVehicle()->SetEnginePower(2, abs_angle*factor + _velocity.GetLength()*factor*0.1f);
				} else if (_path->GetCurrentInterpolationTime() >= DOUBLE_OFF_END_PATH_TIME &&
					IsCloseToTarget(_position, SLOW_DOWN_DISTANCE)) {
					game_->GetVehicle()->SetEnginePower(2, 0.2f);
				}
			}
		} break;
		case kModeBackingUp: {
			// Brake or move backward.
			const bool is_moving_forward = (game_->GetVehicle()->GetForwardSpeed() > 0.1f*SCALE_FACTOR);
			game_->GetVehicle()->SetEnginePower(0, is_moving_forward? 0.0f : -strength);
			game_->GetVehicle()->SetEnginePower(2, is_moving_forward? strength :  0.0f);

			const float back_time = 1.7f;
			if (!is_moving_forward && mode_run_time > back_time) {
				SetMode(kModeHeadingBackOnTrack);
				return;
			}
		} break;
		case kModeBackingUpToGoal: {
			vec3 wanted_direction = game_->GetGoal()->GetPosition() - _position;
			const float distance2 = wanted_direction.GetLengthSquared();
			if (distance2 <= ON_GOAL_DISTANCE*ON_GOAL_DISTANCE) {
				Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
				_path->GotoAbsoluteTime(END_PATH_TIME);
				SetMode(kModeStoppingAtGoal);
				return;
			} else if (distance2 >= TOTALLY_OFF_COURSE_DISTANCE*TOTALLY_OFF_COURSE_DISTANCE) {
				SetMode(kModeFindBestPath);
				return;
			}

			// Brake or move backward.
			const bool is_moving_forward = (game_->GetVehicle()->GetForwardSpeed() > 0.1f*SCALE_FACTOR);
			game_->GetVehicle()->SetEnginePower(0, is_moving_forward? 0.0f : -strength);
			game_->GetVehicle()->SetEnginePower(2, is_moving_forward? strength :  0.0f);

			// Turn steering wheel.
			const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
			float angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
			angle += (angle < 0)? +PIF : -PIF;
			angle *= 3;
			game_->GetVehicle()->SetEnginePower(1, -angle);

			if (mode_run_time > 15) {
				log_.AHeadline("Not getting back to goal. F**k it.");
				SetMode(kModeRotateOnTheSpot);
				return;
			}
		} break;
		case kModeFlee: {
			// Pedal to the metal.
			game_->GetVehicle()->SetEnginePower(0, +strength);
			game_->GetVehicle()->SetEnginePower(1, 0);
			game_->GetVehicle()->SetEnginePower(2, 0);
			if (mode_run_time > 3.0f) {
				SetMode(kModeFindBestPath);
				return;
			}
		} break;
		case kModeStoppingAtGoal:
		case kModeAtGoal: {
			Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
			if (!IsCloseToTarget(_position, ON_GOAL_DISTANCE)) {
				// If either already stopped at goal, OR stopped but at the wrong spot.
				if (mode_ != kModeStoppingAtGoal || game_->GetVehicle()->GetForwardSpeed() < 0.5f*SCALE_FACTOR) {
					_path->GotoAbsoluteTime(DOUBLE_OFF_END_PATH_TIME);	// Close to end, but not at end.
					SetMode(kModeHeadingBackOnTrack);
					return;
				}
			}
			if (mode_ != kModeAtGoal) {
				SetMode(kModeAtGoal);
			}
			// Brake!
			game_->GetVehicle()->SetEnginePower(0, 0);
			game_->GetVehicle()->SetEnginePower(2, -strength);	// Negative = use full brakes, not only hand brake.
		} break;
		case kModeWaitingForElevator: {
			if (mode_run_time > 25.0f) {
				log_.AHeadline("Movin' on, I've waited for the elevator too long.");
				SetMode(kModeFlee);
				return;
			}
			if (::fabs(last_average_angle_) > 0.1f) {
				strength *= SMOOTH_BRAKING_FACTOR;	// Smooth braking when turning, we can always back up if necessary.
			}
			const float elevator_distance2 = elevator_get_on_position_.GetDistanceSquared(_position);
			if (elevator_distance2 < ELEVATOR_TOO_CLOSE_DISTANCE*ELEVATOR_TOO_CLOSE_DISTANCE) {
				log_.AHeadline("Got too close to the elevator stop position, backing up.");
				// Back up parallel to the spline direction.
				const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
				Spline* _path = game_->GetLevel()->QueryPath()->GetPath(active_path_);
				const vec3 wanted_direction = _path->GetSlope();
				const float angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
				game_->GetVehicle()->SetEnginePower(1, +angle);
				const bool is_moving_forward = (game_->GetVehicle()->GetForwardSpeed() > 0.1f*SCALE_FACTOR);
				game_->GetVehicle()->SetEnginePower(0, is_moving_forward? 0.0f : -strength);
				game_->GetVehicle()->SetEnginePower(2, is_moving_forward? strength :  0.0f);

				const cure::Elevator* _nearest_elevator;
				vec3 _nearest_lift_position2d;
				float _elevator_xy_distance2_to_elevator_stop;
				const bool is_elevator_here = HasElevatorArrived(_nearest_elevator, _position.z, _nearest_lift_position2d, _elevator_xy_distance2_to_elevator_stop);
				if (is_elevator_here) {
					SetMode(kModeGetOnElevator);
				} else if (QueryVehicleHindered(_time, _velocity)) {
					last_average_angle_ = (Random::Uniform(0.0f, 1.0f) > 0.5f)? +2.0f : -2.0f;
					SetMode(kModeRotateOnTheSpot);
				}
				return;
			}
			if (::fabs(elevator_get_on_position_.z-_position.z) >= 3 ||
				elevator_distance2 > ELEVATOR_FAR_DISTANCE*ELEVATOR_FAR_DISTANCE) {
				log_.AHeadline("Somehow got away from the elevator wait position, doing something else.");
				SetMode(kModeFindBestPath);
				return;
			}

			// Check that we're headed towards the elevator center.
			if (_velocity.GetLengthSquared() < 0.5f) {
				vec3 up(0, 0, 1);
				up = game_->GetVehicle()->GetOrientation() * up;
				if (up.z > 0.7f) {
					const vec3 _direction = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
					const vec3 wanted_direction = elevator_get_on_position_ - _position;
					const float angle = LEPRA_XY_ANGLE(wanted_direction, _direction);
					if (::fabs(angle) > PIF/12) {
						rotate_angle_ = -angle;
						SetMode(kModeRotateOnTheSpotWaiting);
						return;
					}
				}
			}

			const cure::Elevator* _nearest_elevator;
			vec3 _nearest_lift_position2d;
			float _elevator_xy_distance2_to_elevator_stop;
			if (HasElevatorArrived(_nearest_elevator, _position.z, _nearest_lift_position2d, _elevator_xy_distance2_to_elevator_stop)) {
				vec3 velocity_xy = _nearest_elevator->GetVelocity();
				bool try_get_on = false;
				// Check if elevator is on it's way out.
				if (IsVertical(velocity_xy)) {
					try_get_on = true;
				} else {
					velocity_xy.x *= 0.1f;
					velocity_xy.y *= 0.1f;
					velocity_xy.z  = 0;
					if (_elevator_xy_distance2_to_elevator_stop+0.1f >= elevator_get_on_position_.GetDistanceSquared(_nearest_lift_position2d+velocity_xy)) {
						try_get_on = true;
					}
				}
				if (try_get_on) {
					log_.AInfo("Elevator here - getting on!");
					SetMode(kModeGetOnElevator);
					return;
				} else {
					log_.AInfo("Waiting for elevator: not getting on, since elevator is departing!");
				}
			}

			game_->GetVehicle()->SetEnginePower(1, 0);
			// Brake!
			game_->GetVehicle()->SetEnginePower(0, 0);
			game_->GetVehicle()->SetEnginePower(2, -strength);	// Negative = use full brakes, not only hand brake.
		} break;
		case kModeOnElevator: {
			strength *= SMOOTH_BRAKING_FACTOR;	// Smooth braking, we can always back up if necessary.

			// Brake!
			game_->GetVehicle()->SetEnginePower(0, 0);
			game_->GetVehicle()->SetEnginePower(1, 0);
			game_->GetVehicle()->SetEnginePower(2, -strength);	// Negative = use full brakes, not only hand brake.

			// Check if elevator departed.
			const float minimum_velocity2 = 0.5f*0.5f;
			if (mode_run_time > 0.7f && _velocity.GetLengthSquared() > minimum_velocity2) {
				const cure::Elevator* _nearest_elevator;
				const vec3 nearest_lift_position = GetClosestElevatorPosition(elevator_get_on_position_, _nearest_elevator);
				if (nearest_lift_position.z > _position.z+0.2f) {
					// Crap, we missed it!
					log_.AHeadline("Must have missed the elevator (it's not close!), waiting for it again!");
					SetMode(kModeWaitingForElevator);
					return;
				}
				// Vehicle speed check not enouch (bouncy wheels), so check elevator speed too.
				vec3 elevator_velocity = _nearest_elevator->GetVelocity();
				if (elevator_velocity.GetLengthSquared() > minimum_velocity2) {
					const bool is_horizontal = !IsVertical(elevator_velocity);
					const vec3 _direction = is_horizontal? elevator_velocity : game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
					rotate_angle_ = -GetRelativeDriveOnAngle(_direction);
					if (::fabs(rotate_angle_) > PIF/6 || is_horizontal) {
						if (is_horizontal) {
							rotate_angle_ = (rotate_angle_ < 0)? -1.3f : +1.3f;
						}
						SetMode(kModeRotateOnTheSpotDuring);
						return;
					}
					SetMode(kModeFindPathOffElevator);
					return;
				}
			} else if (mode_run_time > 4.5f) {
				// Crap, we missed it!
				log_.AHeadline("Must have missed the elevator (I'm still here!), waiting for it again!");
				SetMode(kModeWaitingForElevator);
				return;
			}

			if (mode_run_time > 0.8f) {
				// Check if we should adjust pos.
				const vec3 forward = game_->GetVehicle()->GetOrientation() * vec3(0,1,0);
				const float dist = elevator_get_on_position_.GetDistanceSquared(_position);
				if (dist > elevator_get_on_position_.GetDistanceSquared(_position+forward)) {
					game_->GetVehicle()->SetEnginePower(0, +strength);
					game_->GetVehicle()->SetEnginePower(2, 0);
				} else if (dist > elevator_get_on_position_.GetDistanceSquared(_position-forward)) {
					game_->GetVehicle()->SetEnginePower(0, -strength);
					game_->GetVehicle()->SetEnginePower(2, 0);
				}
			}
		} break;
		case kModeRotateOnTheSpot:
		case kModeRotateOnTheSpotDuring:
		case kModeRotateOnTheSpotWaiting: {
			float angle = rotate_angle_;
			const float min_angle = 0.3f;
			if (::fabs(angle) < min_angle) {
				angle = (angle < 0)? -min_angle : +min_angle;
			}
			float steer_end_time = 0.4f;
			float forward_end_time = steer_end_time + 0.9f;
			float other_steer_end_time = forward_end_time + steer_end_time;
			float period = other_steer_end_time + 0.8f;

			// A monster truck's steering impared.
			angle *= 2;
			steer_end_time = 0.7f;
			forward_end_time = steer_end_time + 0.7f;
			other_steer_end_time = forward_end_time + steer_end_time;
			period = other_steer_end_time + 1.0f;
			strength *= SMOOTH_BRAKING_FACTOR;
			if (mode_ == kModeRotateOnTheSpotWaiting) {
				const cure::Elevator* _nearest_elevator;
				vec3 _nearest_lift_position2d;
				float _elevator_xy_distance2_to_elevator_stop;
				if (HasElevatorArrived(_nearest_elevator, _position.z, _nearest_lift_position2d, _elevator_xy_distance2_to_elevator_stop)) {
					log_.AHeadline("Elevator arrived while rotating on the spot, getting on instead!");
					SetMode(kModeGetOnElevator);
					return;
				}
			}
			// Finish this rotation show if we're getting there.
			const int iterations = (mode_ == kModeRotateOnTheSpotWaiting)? 1 : 2;
			if (mode_run_time > iterations*period+steer_end_time) {
				game_->GetVehicle()->SetEnginePower(0, 0);
				game_->GetVehicle()->SetEnginePower(1, -angle);
				game_->GetVehicle()->SetEnginePower(2, -1);
				if (mode_ == kModeRotateOnTheSpot) {
					SetMode(kModeHeadingBackOnTrack);
				} else if (mode_ == kModeRotateOnTheSpotDuring) {
					SetMode(kModeFindPathOffElevator);
				} else {
					SetMode(kModeWaitingForElevator);
				}
				return;
			}
			for (int x = 0; x < iterations+1; ++x) {
				const float base = x*period;
				if (mode_run_time >= base && mode_run_time < base+steer_end_time) {
					// Brake and turn in "forward direction".
					game_->GetVehicle()->SetEnginePower(0, 0);
					game_->GetVehicle()->SetEnginePower(1, -angle);
					game_->GetVehicle()->SetEnginePower(2, -strength);
					break;
				} else if (mode_run_time >= base+steer_end_time && mode_run_time < base+forward_end_time) {
					// Drive forward.
					game_->GetVehicle()->SetEnginePower(0, +strength);
					game_->GetVehicle()->SetEnginePower(2, 0);
					break;
				} else if (mode_run_time >= base+forward_end_time && mode_run_time < base+other_steer_end_time) {
					// Brake and turn in "backward direction".
					game_->GetVehicle()->SetEnginePower(0, 0);
					game_->GetVehicle()->SetEnginePower(1, +angle);
					game_->GetVehicle()->SetEnginePower(2, -strength);
					break;
				} else if (mode_run_time >= base+other_steer_end_time && mode_run_time < base+period) {
					// Drive backward.
					game_->GetVehicle()->SetEnginePower(0, -0.7f*strength);
					game_->GetVehicle()->SetEnginePower(2, 0);
					break;
				}
			}
		} break;
	}