// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C)      2022 by Kart Krew.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  r_bbox.c
/// \brief Boundary box (cube) renderer

#include "doomdef.h"
#include "command.h"
#include "r_local.h"
#include "screen.h" // cv_renderhitbox
#include "v_video.h" // V_DrawFill

enum {
	RENDERHITBOX_OFF,
	RENDERHITBOX_TANGIBLE,
	RENDERHITBOX_ALL,
	RENDERHITBOX_INTANGIBLE,
	RENDERHITBOX_RINGS,
};

static CV_PossibleValue_t renderhitbox_cons_t[] = {
	{RENDERHITBOX_OFF, "Off"},
	{RENDERHITBOX_TANGIBLE, "Tangible"},
	{RENDERHITBOX_ALL, "All"},
	{RENDERHITBOX_INTANGIBLE, "Intangible"},
	{RENDERHITBOX_RINGS, "Rings"},
	{0}};

consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", CV_CHEAT|CV_NOTINNET, renderhitbox_cons_t, NULL);
consvar_t cv_renderhitboxinterpolation = CVAR_INIT ("renderhitbox_interpolation", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_renderhitboxgldepth = CVAR_INIT ("renderhitbox_gldepth", "Off", CV_SAVE, CV_OnOff, NULL);

struct bbox_col {
	INT32 x;
	INT32 y;
	INT32 h;
};

struct bbox_config {
	fixed_t height;
	fixed_t tz;
	struct bbox_col col[4];
	UINT8 color;
};

static inline void
raster_bbox_seg
(		INT32 x,
		fixed_t y,
		fixed_t h,
		UINT8 pixel)
{
	y /= FRACUNIT;

	if (y < 0)
		y = 0;

	h = y + (FixedCeil(abs(h)) / FRACUNIT);

	if (h >= viewheight)
		h = viewheight;

	while (y < h)
	{
		topleft[x + y * vid.width] = pixel;
		y++;
	}
}

static void
draw_bbox_col
(		struct bbox_config * bb,
		size_t p,
		fixed_t tx,
		fixed_t ty)
{
	struct bbox_col *col = &bb->col[p];

	fixed_t xscale, yscale;

	if (ty < FRACUNIT) // projection breaks down here
		ty = FRACUNIT;

	xscale = FixedDiv(projection, ty);
	yscale = FixedDiv(projectiony, ty);

	col->x = (centerxfrac + FixedMul(tx, xscale)) / FRACUNIT;
	col->y = (centeryfrac - FixedMul(bb->tz, yscale));
	col->h = FixedMul(bb->height, yscale);

	// Using this function is TOO EASY!
	V_DrawFill(
			viewwindowx + col->x,
			viewwindowy + col->y / FRACUNIT, 1,
			col->h / FRACUNIT, V_NOSCALESTART | bb->color);
}

static void
draw_bbox_row
(		struct bbox_config * bb,
		size_t p1,
		size_t p2)
{
	struct bbox_col
		*a = &bb->col[p1],
		*b = &bb->col[p2];

	INT32 x1, x2; // left, right
	INT32 dx; // width

	fixed_t y1, y2; // top, bottom
	fixed_t s1, s2; // top and bottom increment

	if (a->x > b->x)
	{
		struct bbox_col *c = a;
		a = b;
		b = c;
	}

	x1 = a->x;
	x2 = b->x;

	if (x1 == x2 || x1 >= viewwidth || x2 < 0)
		return;

	dx = x2 - x1;

	y1 = a->y;
	y2 = b->y;
	s1 = (y2 - y1) / dx;

	y2 = y1 + a->h;
	s2 = ((b->y + b->h) - y2) / dx;

	// FixedCeil needs a minimum!!! :D :D

	if (s1 == 0)
		s1 = 1;

	if (s2 == 0)
		s2 = 1;

	if (x1 < 0)
	{
		y1 -= x1 * s1;
		y2 -= x1 * s2;
		x1 = 0;
	}

	if (x2 >= viewwidth)
		x2 = viewwidth - 1;

	while (x1 < x2)
	{
		raster_bbox_seg(x1, y1, s1, bb->color);
		raster_bbox_seg(x1, y2, s2, bb->color);

		y1 += s1;
		y2 += s2;

		x1++;
	}
}

UINT8 R_GetBoundingBoxColor(mobj_t *thing)
{
	UINT32 flags = thing->flags;

	if (thing->player)
		return 255; // 0FF

	if (flags & (MF_NOCLIPTHING))
		return 7; // BFBFBF

	if (flags & (MF_BOSS|MF_ENEMY))
		return 35; // F00

	if (flags & (MF_MISSILE|MF_PAIN))
		return 54; // F70

	if (flags & (MF_SPECIAL|MF_MONITOR))
		return 73; // FF0

	if (flags & MF_PUSHABLE)
		return 112; // 0F0

	if (flags & (MF_SPRING))
		return 181; // F0F

	if (flags & (MF_NOCLIP))
		return 152; // 00F

	return 0; // FFF
}

void R_DrawThingBoundingBox(vissprite_t *vis)
{
	// radius offsets
	fixed_t rs = vis->scale;
	fixed_t rc = vis->xscale;

	// translated coordinates
	fixed_t tx = vis->gx;
	fixed_t ty = vis->gy;

	struct bbox_config bb = {
		.height = vis->thingheight,
		.tz = vis->texturemid,
		.color = R_GetBoundingBoxColor(vis->mobj),
	};

	// 1--3
	// |  |
	// 0--2

	// left

	draw_bbox_col(&bb, 0, tx, ty); // bottom
	draw_bbox_col(&bb, 1, tx - rc, ty + rs); // top

	// right

	tx += rs;
	ty += rc;

	draw_bbox_col(&bb, 2, tx, ty); // bottom
	draw_bbox_col(&bb, 3, tx - rc, ty + rs); // top

	// connect all four columns

	draw_bbox_row(&bb, 0, 1);
	draw_bbox_row(&bb, 1, 3);
	draw_bbox_row(&bb, 3, 2);
	draw_bbox_row(&bb, 2, 0);
}

static boolean is_tangible (mobj_t *thing)
{
	// These objects can never touch another
	if (thing->flags & (MF_NOCLIPTHING))
	{
		return false;
	}

	// These objects probably do nothing! :D
	if ((thing->flags & (MF_SPECIAL|MF_SOLID|MF_SHOOTABLE
					|MF_PUSHABLE|MF_BOSS|MF_MISSILE|MF_SPRING
					|MF_BOUNCE|MF_MONITOR|MF_FIRE|MF_ENEMY
					|MF_PAIN|MF_STICKY
					|MF_GRENADEBOUNCE)) == 0U)
	{
		return false;
	}

	return true;
}

boolean R_ThingBoundingBoxVisible(mobj_t *thing)
{
	INT32 cvmode = cv_renderhitbox.value;
	boolean ring = false;

	if (multiplayer) // No hitboxes in multiplayer to avoid cheating
		return false;

	switch (thing->type)
	{
		default:
			// First person / awayviewmobj -- rendering a bbox
			// too close to the viewpoint causes anomalies
			// and these are exactly on the viewpoint!
			if (thing != r_viewmobj)
			{
				break;
			}
			// FALLTHRU

		case MT_SKYBOX:
			// Ditto for skybox viewpoint but because they
			// are rendered using portals in Software,
			// r_viewmobj does not point here.
			return false;

		case MT_RING:
		case MT_BLUESPHERE:
		case MT_NIGHTSSTAR:
		case MT_NIGHTSCHIP:
		case MT_COIN:
			// Rings and similar objects are often placed
			// in large amounts, so they are handled
			// separately from other tangible objects.
			ring = true;
			break;
	}

	switch (cvmode)
	{
		case RENDERHITBOX_OFF:
			return false;

		case RENDERHITBOX_ALL:
			return true;

		case RENDERHITBOX_INTANGIBLE:
			return !is_tangible(thing);

		case RENDERHITBOX_TANGIBLE:
			return !ring && is_tangible(thing);

		case RENDERHITBOX_RINGS:
			return ring;

		default:
			return false;
	}
}
