Cameras and Movement

3D Camera

Simple 3D camera that changes different curves when player is walking, running or in stealth mode. The camera doesn't look directly at the main character, but a little to their right.


#include "common.h"
void TCompCamera3D::load(const json& j, TEntityParseContext& ctx)
{

	target_name = j.value("target", target_name);

	curveFilename = j["curve"];
	curve = Resources.get(curveFilename)->as();

	farCurveFilename = j.value("farCurve", farCurveFilename);
	if (farCurveFilename != "") {
		farCurve = Resources.get(farCurveFilename)->as();
	}

	offset = j.value("offset", offset);
	height = j.value("height",height);
	leftOffset = j.value("leftOffset", leftOffset);
	uses_camera_left = j.value("usesCameraLeft", uses_camera_left);
	_pitchRatio = j.value("pitchRatio", _pitchRatio);
	_sensitivity = j.value("sensitivity", _sensitivity);
	_padSensitivity = j.value("padsensitivity", _padSensitivity);
	camera_name = j.value("name", camera_name);
}

void TCompCamera3D::update(float scaled_dt)
{

	timer_projection += scaled_dt;

	CEntity* hplayer = (CEntity*)getEntityByName("Player");
	TCompPlayer* comp_player = hplayer->get();
	CEntity* player_entity;
	TCompTransform* player_trans;
	TCompTransform* camera_trans = get();
	CEntity* hCamDir = (CEntity*)getEntityByName("camera_direction");
	if (!camera_trans) return;

	//get target entity
	fetchTarget();

	player_entity = target;
	if (!player_entity) return; //the target takes one frame to exist, so we wait until it exists
	player_trans = player_entity->get();
	
	if (comp_player->getModoCamera() == CAM3D /*|| !comp_player->focusToShadow()*/) {
		//leo el input del movimiento del raton
		readInput(scaled_dt);
		without_init = true;
		//dbg("I'm in 3D mode\n");
	}
	else {

		if (without_init) {
			timer_projection = 0;
			without_init = false;
			//dbg("Just started 2D mode\n");
		}

		if (timer_projection >= time_to_changeCamera) {
			
			CEntity* hCam2d = (CEntity*)getEntityByName("camera2D");
			TCompTransform* cam2d_trans = hCam2d->get();
			float yaw2d, pitch2d;
			cam2d_trans->getEulerAngles(&yaw2d, &pitch2d, nullptr);
			_yaw = yaw2d;
			_replacePitch = pitch2d;
		}
		else {
			//dbg("I'm in 2D mode but I haven't change cameras yet\n");
		}

	}


	TCompCamera* myCam = get();//para hacer los calculos de las colisiones luego
	VEC3 oldPos = camera_trans->getPosition();//para interpolar

	
	const MAT44 tr = MAT44::CreateTranslation({ 0.f, 0.f, offset });
	const MAT44 rt = MAT44::CreateFromYawPitchRoll(_yaw, _replacePitch, 0.f);
	_replacePitch = 0.f;
	MAT44 world;
	
		
	VEC3 curvePos, newLookAt;

	world = rt * tr * MAT44::CreateScale(player_trans->getScale())
		* MAT44::CreateFromQuaternion(QUAT(0, 0, 0, 0))
		* MAT44::CreateTranslation(player_trans->getPosition());

	//la curva se evalua en funcion de la matriz y el input vertical del movimiento del raton
	float yawcam, pitchcam;
	camera_trans->getEulerAngles(&yawcam, &pitchcam, nullptr);
	float yawCam_deg = rad2deg(yawcam);	

	float yawPlayer, pitchPlayer;
	player_trans->getEulerAngles(&yawPlayer, &pitchPlayer, nullptr);
	float yawPlayer_deg = rad2deg(yawPlayer);


	curvePos = calculateCurvePos(world, yawCam_deg, yawPlayer_deg);
	//curvePos = curve->evaluate(world, _pitchRatio);



	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// MAKE CAMERA LOOK TO THE RIGHT OF THE PLAYER

	TCompTransform* camDir_trans = hCamDir->get();

	newLookAt = player_trans->getPosition() - camDir_trans->getLeft() * leftOffset;
	newLookAt.y += 1;

	curvePos = curvePos - camDir_trans->getLeft() * leftOffset;

	

	//check camera collisions
	VEC3 newPos = myCam->HandleCollisionZoom(curvePos, newLookAt, 0.3);

	if (newPos != curvePos) {
		hitCamera = true;
	}
	else {
		hitCamera = false;
	}


	//interpolate
	VEC3 camPos = 0.8f * oldPos + 0.2f * newPos;
	VEC3 lookAt = 0.8f * oldLookAt + 0.2f * newLookAt;
	
	camera_trans->lookAt(camPos , lookAt);
	oldLookAt = lookAt;

}

void TCompCamera3D::registerMsgs()
{
	DECL_MSG(TCompCamera3D, TMsgTargetChange, addEnemyTarget);
	
}




void TCompCamera3D::readInput(float dt)
{
	input::CInputModule& input = CEngine::get().getInput();

	if (cameraFixed) return;

	if (input["camera_x"].isPressed() or input["camera_y"].isPressed()) {
		VEC2 mOff = VEC2(input["camera_x"].value, input["camera_y"].value);
		mOff *= _padSensitivity;
		_yaw += mOff.x * _sensitivity * 3;
		_pitchRatio += mOff.y * _sensitivity;
	}
	else {
		VEC2 mOff = input.getMouse().deltaPosition * 10; // multiply the mouse input so the camera doesn't go extremeley slow
		_yaw += -mOff.x * _sensitivity * 3;
		_pitchRatio += mOff.y * _sensitivity;
	}

	_pitchRatio = clamp(_pitchRatio, 0.f, 1.f);
}


void  TCompCamera3D::fetchTarget()
{

	target = getEntityByName(target_name);
}

VEC3 TCompCamera3D::calculateCurvePos(MAT44 world, float yawCamera, float yawPlayer)
{
	//if there's no far curve, go back to normal curve
	if (farCurve == nullptr) {
		using_far = false;
		return curve->evaluate(world, _pitchRatio);
	}

	//default values when angle to player is 0
	//left to centre
	float limLateral = 90;
	float limCentral = 180;
	//right to centre
	float limLateral2 = -90;
	float limCentral2 = -180;

	if (yawPlayer != 0) {
		calculateLimitsFarCam(yawPlayer, &limCentral, &limLateral, &limLateral2);
		limCentral2 = limCentral;
	}
	float weight = calculateWeightFarCam(yawCamera, limCentral, limCentral2, limLateral, limLateral2);

	// Variables debug
	lc = limCentral;
	l1 = limLateral;
	l2 = limLateral2;
	////////////////////


	//if weight is 0 no blending needed
	if (weight < 0) {
		using_far = false;
		return curve->evaluate(world, _pitchRatio);
	}
	else {
		VEC3 curveStealthPos = curve->evaluate(world, _pitchRatio);
		VEC3 curveFarStealthPos = farCurve->evaluate(world, _pitchRatio);

		using_far = true;
		return VEC3::Lerp(curveStealthPos, curveFarStealthPos, weight);
	}

}

void TCompCamera3D::calculateLimitsFarCam(float yawPlayer, float* limitCentral, float* limitLat1, float* limitLat2)
{
	float c1 = 180, c2 = -180, c3 = 90, c4 = -90;

	if (yawPlayer < 0) {

		*limitCentral = c1 - std::abs(yawPlayer);
		*limitLat1 = *limitCentral - c3;
		*limitLat2 = *limitCentral - c4;
		if (*limitLat2 > c1) {
			*limitLat2 = c2 + (*limitLat2 - c1);
		}

	}
	else {

		*limitCentral = c2 + yawPlayer;
		*limitLat1 = *limitCentral - c3;
		if (*limitLat1 < c2) {
			*limitLat1 = c1 - std::abs(*limitLat1 - c2);
		}
		*limitLat2 = *limitCentral - c4;
	}
}

float TCompCamera3D::calculateWeightFarCam(float yawCamera, float limitCentral, float limitCentral2, float limitLat1, float limitLat2)
{
	float w;

	if (isInRange(limitCentral, limitLat1, yawCamera)) {

		float c = limitCentral;
		float l = limitLat1;
		float yc = yawCamera;

		int qLat = checkQuadrant(limitLat1);
		int qCen = checkQuadrant(limitCentral);


		if (qLat == 2 && qCen == 4) {
			c = 180;
			l = 90;
			if (yawCamera > 0) {
				yc = l + (yawCamera- limitLat1);
			}
			else {
				yc = c - abs(limitCentral - yawCamera);
			}
		}
		else if (qLat == 4 && qCen == 2) {
			c = 90;
			l = 180;

			if (yawCamera > 0) {
				yc = c + (yawCamera - limitCentral);
			}
			else {
				yc = l - abs(limitLat1 - yawCamera);
			}
		}


		c -= 15;
		if (c < (-180))	c = -180;

		w = (100 * (yc - l)) / (c - l);
		w = w / 100; // Pasamos el peso a 0.0 -> 1.0

		if (w > 1)	w = 1;
		if (w < 0)	w = 0;

		weightDebug = w;

	}
	else if (isInRange(limitCentral2, limitLat2, yawCamera)) {

		float c = limitCentral2;
		float l = limitLat2;
		float yc = yawCamera;

		int qLat = checkQuadrant(limitLat2);
		int qCen = checkQuadrant(limitCentral2);


		if (qLat == 2 && qCen == 4) {
			c = 180;
			l = 90;

bool TCompCamera3D::isInRange(float limitCentral, float limitLat, float yawCamera)
{

	//Check if camera is in cuadrants 2 and 4 
	//it will give us the range limit from 180, -180 to limit

	int qLat = checkQuadrant(limitLat);
	int qCen = checkQuadrant(limitCentral);

	float min, max;

	if (qLat == 2 && qCen == 4 || qLat == 4 && qCen == 2) {

		if (qLat == 4) {
			min = limitLat;
			max = limitCentral;
		}
		else {
			min = limitCentral;
			max = limitLat;
		}

		if ((yawCamera >= max && yawCamera <= 180) || (yawCamera >= -180 && yawCamera <= min))	return true;
		else return false;
	}
	else {
		if (limitCentral > limitLat) {
			min = limitLat;
			max = limitCentral;
		}
		else {
			min = limitCentral;
			max = limitLat;
		}

		if (yawCamera >= min && yawCamera <= max)	return true;
		else return false;

	}

	return false;
}

int TCompCamera3D::checkQuadrant(float yaw)
{

	if (yaw >= 0 && yaw <= 90)	return 1;
	if (yaw > 90 && yaw <= 180)	return 2;
	if (yaw < 0 && yaw >= -90)	return 3;
	if (yaw < -90 && yaw >= -180) return 4;
	else return 0;

}
											
											

2d Camera

This camera was trickier, as "2D mode" is actually controlling the main character's shadow. The camera had to always look at the shadow in the wall, but never lose sight of the 3D body.




//the camera will focus on the shadow of the player but with the body of the player in sight
void TCompCamera2D::renderCam2D(VEC3 wall_normal, VEC3 wall_point)
{
	TCompPlayer* player = hplayer->get();
	TCompTransform* c_transform = get();
	TCompTransform* htrans = hplayer->get();
	TCompCamera* myCam = get();

	if (!c_transform) return;
	VEC3 oldPos = c_transform->getPosition();

	VEC3 newPos, look;

	//am I going left to right
			//we calculate the distance from the shadow to the body
	distance_shadow_body = VEC3::Distance(htrans->getPosition(), wall_point);
	distance_shadow_body = float_one_point_round(distance_shadow_body);
	
	/* if (distance_shadow_body > 30)
		distance_shadow_body *=0.8;*/
		//we try to get a line that goes from the body or shadow to wherever we want the camera to go
	s = (distance_shadow_body / 2) / sen20;
	//with that s, we get the line that goes from the center point of shadow and body to S in what should be a circle that englobes shadow, body and camera
	distance_center_line = s * cos20;
	distance_center_line = float_one_point_round(distance_center_line);
	//to make sure the camera doesn't go behind the wall we check both possible camera points and the closest to a point outside the wall will be the chosen one
	VEC3 wall_normal_2;
	wall_normal_2 = wall_normal * 2; //a point outside the wall, using its normal
	float radius = s * sen20 / sen140; //this will be the radius of the circle that englobes shadow, player and camera
	//we get the centre point between shadow and body

	float distance_centre_pointX = (htrans->getPosition().x + wall_point.x) / 2;
	float distance_centre_pointZ = (htrans->getPosition().z + wall_point.z) / 2;
	//and the perpendicular vector of the line that goes from shadow to body
	float perpendicular_distanceX = -(wall_point.z - htrans->getPosition().z) / distance_shadow_body;
	float perpendicular_distanceZ = (wall_point.x - htrans->getPosition().x) / distance_shadow_body;
	//we try to get the centre of the circle that englobes shadow, body and camera, there are two possibilities
	VEC3 centrePoint = VEC3(distance_centre_pointX, htrans->getPosition().y + 1, distance_centre_pointZ) + VEC3(perpendicular_distanceX, htrans->getPosition().y + 1, perpendicular_distanceZ) * (distance_center_line - radius);
	VEC3 centrePoint2 = VEC3(distance_centre_pointX, htrans->getPosition().y + 1, distance_centre_pointZ) - VEC3(perpendicular_distanceX, htrans->getPosition().y + 1, perpendicular_distanceZ) * (distance_center_line - radius);
	//calculate each possibility with that normalPoint
	float dis_centre1_normal = VEC3::Distance(centrePoint, wall_normal_2);
	float dis_centre2_normal = VEC3::Distance(centrePoint2, wall_normal_2);

	VEC3 myCentrePoint;
	//the closest one will be the camera that is in front of the wall
	if (dis_centre1_normal < dis_centre2_normal) {
		myCentrePoint = centrePoint;
	}
	else {
		myCentrePoint = centrePoint2;
	}
	//now we need the angle between the vectors that go shadow to centre and the normal of the wall
	VEC3 shadow_to_centre = (myCentrePoint - wall_point) / radius; //calculate line that goes shadow to centre
	VEC3 unit_wall_normal = wall_normal / wall_normal.Length();//get the normal in units of 1 and 0 (unitize)
	//to get the angle between two vectors we need to do the acosen of the dot product of such vectors
	float dotProduct = unit_wall_normal.Dot(shadow_to_centre);
	
	// Change the distance shadow to camera for absolute value -> dis_shadow_to_camera = distance_shadow_body * 1.3;
	dis_shadow_to_camera = distance_shadow_body + offset_shadow_to_camera;
	if (dis_shadow_to_camera < min_dis_shadow_to_camera) dis_shadow_to_camera = min_dis_shadow_to_camera;

	float dif_in_y = htrans->getPosition().y - wall_point.y + 1;
	//the position of the camera will now be the distance calculated multiplied by the direction of the wall normal and the position of the shadow  

	newPos = debug_camPos = (unit_wall_normal * dis_shadow_to_camera) + wall_point + VEC3(0, dif_in_y, 0);
	look = debug_look = wall_point;

	VEC3 wallRight = VEC3::Up.Cross(wall_normal);

	calculateMovementOffset(&newPos, &look, wallRight);

	debug_cam_pos = newPos;
	debug_cam_look = look;

	
	if (without_init) {
		without_init = false;
		camPos = newPos;
		newLook = look;
	}
	else {
		camPos = 0.9f * oldPos + 0.1f * newPos;
		newLook = 0.9f * oldLook + 0.1f * look;
	}

}
										

Moving boxes

Using physx, casting a ray to see if the main character in front of a pushable box, that box converts into a box controller, moving like the character does. Once it is released, it turns back into a dynamic object.




void TCompPlayer::hitBox(const TMsgPush& msg_push) {
	push_box = msg_push.object;
}
SoundEvent* move_box_sound = nullptr;
void TCompPlayer::convertPushBox(bool create_controller) {
	CEntity* ent_box = push_box;
	if (ent_box == nullptr)	return;

	TCompCollider* box_col = ent_box->get();
	if (move_box_sound) {
		move_box_sound->stop();
		move_box_sound = nullptr;
	}
	if (create_controller && !box_col->boxcontroller) {
		box_col->destroyActor();
		box_col->recreateController();
	}
	else if(!create_controller){
	
		box_col->destroyBoxController();
		box_col->recreateActorBox();
		push_box = CHandle();
	}
}

void TCompPlayer::movePushBox(float dt) {
	CEntity* ent_box = push_box;
	//assert(ent_box);
	if (ent_box == nullptr) return;
	TCompTransform* t_box = ent_box->get();
	VEC3 pos = t_box->getPosition();
	TCompCollider* box_col = ent_box->get();
	//assert(box_col->boxcontroller);
	if (box_col->boxcontroller == nullptr) return;
	float dif = VEC3::Distance(pos, oldPos);
	CEngine::get().getPhysics().move(velocity, dt, box_col->boxcontroller, box_collisions);
	oldPos = pos;
	if (velocity.x != 0 && velocity.z != 0 && dif>0.01) {
		TCompAudio* audio = get();
		if (move_box_sound)
		{
			if (move_box_sound->isPlaying()) return;
			move_box_sound = audio->playEvent("moveObject");
		}
		else {
			move_box_sound = audio->playEvent("moveObject");
		}

	}
	else {
		if (!move_box_sound)return;
		move_box_sound->stop();

	}
	
}

bool TCompPlayer::canMoveTheBox(){
	PxRaycastBuffer hitCall;
	PxQueryFilterData fd = PxQueryFilterData();
	fd.data.word0 = CModulePhysicsSinn::FilterGroup::Projectable_Object;

	TCompTransform* player_trans = get();
	VEC3 player_front = player_trans->getFront(); // Deberia estar ya normalizado
	//player_front.Normalize(); // Deberiamos poderlo quitar
	VEC3 origin = player_trans->getPosition();
	origin.y += 0.5;
	
	bool status = CEngine::get().getPhysics().gScene->raycast(
		VEC3_TO_PXVEC3(origin), VEC3_TO_PXVEC3(player_front),
		4, hitCall, PxHitFlags(PxHitFlag::eDEFAULT), fd);
	if (!status) return status;
	if (!hitCall.hasAnyHits()) {
		if (move_box_sound) {
			move_box_sound->stop();
			move_box_sound = nullptr;
		}
			
		return false;
	}
	CHandle h_collider;
	h_collider.fromVoidPtr(hitCall.block.actor->userData);
	TCompCollider* c_collider = h_collider;
	const json& jconfig = c_collider->jconfig;

	if (jconfig.count("controllerBox") > 0) {
		TCompTransform* box_trans = c_collider->get();
		VEC3 half_size = VEC3::Zero;
		if (jconfig.count("shapes")) {
			const json& jshapes = jconfig["shapes"];
			for (const json& jshape : jshapes) {
				std::string jgeometryType = jshape["shape"];
				if (jgeometryType == "box") {
					VEC3 aux = loadVEC3(jshape, "half_size");
					if (aux.x + aux.y + aux.z > half_size.x + half_size.y + half_size.z)
						half_size = aux;
				}
			}			
		}
		if ((1.4f / (half_size.y * 2)) > 1) {
			return true;
		}
	}

}