/*
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
 * It is copyright by its individual contributors, as recorded in the
 * project's Git history.  See COPYING.txt at the top level for license
 * terms and a link to the Git history.
 */
/*
 * 
 * Routines for point definition, rotation, etc.
 * 
 */


#include "3d.h"
#include "globvars.h"

namespace dcx {

//code a point.  fills in the p3_codes field of the point, and returns the codes
ubyte g3_code_point(g3s_point &p)
{
	ubyte cc=0;

	if (p.p3_x > p.p3_z)
		cc |= CC_OFF_RIGHT;

	if (p.p3_y > p.p3_z)
		cc |= CC_OFF_TOP;

	if (p.p3_x < -p.p3_z)
		cc |= CC_OFF_LEFT;

	if (p.p3_y < -p.p3_z)
		cc |= CC_OFF_BOT;

	if (p.p3_z < 0)
		cc |= CC_BEHIND;

	return p.p3_codes = cc;

}

//rotates a point. returns codes.  does not check if already rotated
ubyte g3_rotate_point(g3s_point &dest,const vms_vector &src)
{
	const auto tempv = vm_vec_sub(src,View_position);
	vm_vec_rotate(dest.p3_vec,tempv,View_matrix);
	dest.p3_flags = 0;	//no projected
	return g3_code_point(dest);
}

//checks for overflow & divides if ok, fillig in r
//returns true if div is ok, else false
std::optional<int32_t> checkmuldiv(fix a,fix b,fix c)
{
	const int64_t a64 = a;
	const int64_t b64 = b;
	/* product will be negative if and only if the sign bits of the input
	 * values require it.  Storing the result in a 64-bit value ensures that
	 * overflow cannot occur, and so the sign bit cannot be incorrectly set as
	 * a side effect of overflow.
	 */
	const int64_t product = a64 * b64;
	/* absolute_product will be positive, because the only negative number that
	 * remains negative after negation is too large to be produced by the
	 * multiplication of 2 32-bit signed inputs.
	 */
	const auto absolute_product = (product < 0) ? -product : product;
	if ((absolute_product >> 31) >= c)
		/* If this branch is taken, then the division would produce a value
		 * that cannot be correctly represented in `int32_t`.  Return a failure
		 * code, rather than returning an incorrect result.  This case is
		 * tested explicitly, rather than the clearer construct of:

		const auto result = q.q / static_cast<int64_t>(c);
		if (static_cast<int32_t>(result) != result)
			return std::nullopt;

		 * because that would always perform the division, before determining
		 * whether the division is valid.
		 */
		return std::nullopt;
	else {
		const int64_t c64 = c;
		return static_cast<int32_t>(product / c64);
	}
}

//projects a point
void g3_project_point(g3s_point &p)
{
#ifndef __powerc
	if ((p.p3_flags & PF_PROJECTED) || (p.p3_codes & CC_BEHIND))
		return;

	const auto pz = p.p3_z;
	const auto otx = checkmuldiv(p.p3_x, Canv_w2, pz);
	std::optional<int32_t> oty;
	if (otx && (oty = checkmuldiv(p.p3_y, Canv_h2, pz)))
	{
		p.p3_sx = Canv_w2 + *otx;
		p.p3_sy = Canv_h2 - *oty;
		p.p3_flags |= PF_PROJECTED;
	}
	else
		p.p3_flags |= PF_OVERFLOW;
#else
	double fz;
	
	if ((p.p3_flags & PF_PROJECTED) || (p.p3_codes & CC_BEHIND))
		return;
	
	if ( p.p3_z <= 0 )	{
		p.p3_flags |= PF_OVERFLOW;
		return;
	}

	fz = f2fl(p.p3_z);
	p.p3_sx = fl2f(fCanv_w2 + (f2fl(p.p3_x)*fCanv_w2 / fz));
	p.p3_sy = fl2f(fCanv_h2 - (f2fl(p.p3_y)*fCanv_h2 / fz));

	p.p3_flags |= PF_PROJECTED;
#endif
}

//from a 2d point, compute the vector through that point
void g3_point_2_vec(vms_vector &v,short sx,short sy)
{
	vms_vector tempv;
	vms_matrix tempm;

	tempv.x =  fixmuldiv(fixdiv((sx<<16) - Canv_w2,Canv_w2),Matrix_scale.z,Matrix_scale.x);
	tempv.y = -fixmuldiv(fixdiv((sy<<16) - Canv_h2,Canv_h2),Matrix_scale.z,Matrix_scale.y);
	tempv.z = f1_0;

	vm_vec_normalize(tempv);
	tempm = vm_transposed_matrix(Unscaled_matrix);
	vm_vec_rotate(v,tempv,tempm);
}

void g3_rotate_delta_vec(vms_vector &dest,const vms_vector &src)
{
	vm_vec_rotate(dest,src,View_matrix);
}

ubyte g3_add_delta_vec(g3s_point &dest,const g3s_point &src,const vms_vector &deltav)
{
	vm_vec_add(dest.p3_vec,src.p3_vec,deltav);
	dest.p3_flags = 0;		//not projected
	return g3_code_point(dest);
}

//calculate the depth of a point - returns the z coord of the rotated point
fix g3_calc_point_depth(const vms_vector &pnt)
{
	quadint q;
	q.q = 0;
	fixmulaccum(&q,(pnt.x - View_position.x),View_matrix.fvec.x);
	fixmulaccum(&q,(pnt.y - View_position.y),View_matrix.fvec.y);
	fixmulaccum(&q,(pnt.z - View_position.z),View_matrix.fvec.z);
	return fixquadadjust(&q);
}

}
