/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 *
 * ET: Legacy
 * Copyright (C) 2012-2024 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ET: Legacy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL Source Code is also
 * subject to certain additional terms. You should have received a copy
 * of these additional terms immediately following the terms and conditions
 * of the GNU General Public License which accompanied the source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file cm_load.c
 * @brief Model loading
 */

#include "cm_local.h"

// to allow boxes to be treated as brush models, we allocate
// some extra indexes along with those needed by the map
#define BOX_LEAF_BRUSHES    1   // ydnar
#define BOX_BRUSHES     1
#define BOX_SIDES       6
#define BOX_LEAFS       2
#define BOX_PLANES      12

clipMap_t cm;
int       c_pointcontents;
int       c_traces, c_brush_traces, c_patch_traces;

byte *cmod_base;

cvar_t *cm_noAreas;
cvar_t *cm_noCurves;
cvar_t *cm_playerCurveClip;
cvar_t *cm_optimize;
cvar_t *cm_optimizePatchPlanes;

cmodel_t box_model;
cplane_t *box_planes;
cbrush_t *box_brush;

void CM_InitBoxHull(void);
void CM_FloodAreaConnections(void);

/**
===============================================================================
                    MAP LOADING
===============================================================================
*/

/**
 * @brief CMod_LoadShaders
 * @param[in] l
 */
void CMod_LoadShaders(lump_t *l)
{
	dshader_t *in;
	int       count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadShaders: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	if (count < 1)
	{
		Com_Error(ERR_DROP, "CMod_LoadShaders: Map with no shaders");
	}
	cm.shaders    = Hunk_Alloc(count * sizeof(*cm.shaders), h_high);
	cm.numShaders = count;

	Com_Memcpy(cm.shaders, in, count * sizeof(*cm.shaders));

	if (LittleLong(1) != 1)
	{
		int i;

		dshader_t *out = cm.shaders;
		for (i = 0 ; i < count ; i++, in++, out++)
		{
			out->contentFlags = LittleLong(out->contentFlags);
			out->surfaceFlags = LittleLong(out->surfaceFlags);
		}
	}
}

/**
 * @brief CMod_LoadSubmodels
 * @param[in] l
 */
void CMod_LoadSubmodels(lump_t *l)
{
	dmodel_t *in;
	cmodel_t *out;
	int      i, j, count;
	int      *indexes;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadSubmodels: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	if (count < 1)
	{
		Com_Error(ERR_DROP, "CMod_LoadSubmodels: Map with no models");
	}
	cm.cmodels      = Hunk_Alloc(count * sizeof(*cm.cmodels), h_high);
	cm.numSubModels = count;

	if (count > MAX_SUBMODELS)
	{
		Com_Error(ERR_DROP, "CMod_LoadSubmodels: MAX_SUBMODELS exceeded");
	}

	for (i = 0 ; i < count ; i++, in++, out++)
	{
		out = &cm.cmodels[i];

		for (j = 0 ; j < 3 ; j++) // spread the mins / maxs by a pixel
		{
			out->mins[j] = LittleFloat(in->mins[j]) - 1;
			out->maxs[j] = LittleFloat(in->maxs[j]) + 1;
		}

		if (i == 0)
		{
			continue;   // world model doesn't need other info
		}

		// make a "leaf" just to hold the model's brushes and surfaces
		out->leaf.numLeafBrushes = LittleLong(in->numBrushes);
		indexes                  = Hunk_Alloc(out->leaf.numLeafBrushes * 4, h_high);
		out->leaf.firstLeafBrush = indexes - cm.leafbrushes;
		for (j = 0 ; j < out->leaf.numLeafBrushes ; j++)
		{
			indexes[j] = LittleLong(in->firstBrush) + j;
		}

		out->leaf.numLeafSurfaces  = LittleLong(in->numSurfaces);
		indexes                    = Hunk_Alloc(out->leaf.numLeafSurfaces * 4, h_high);
		out->leaf.firstLeafSurface = indexes - cm.leafsurfaces;
		for (j = 0 ; j < out->leaf.numLeafSurfaces ; j++)
		{
			indexes[j] = LittleLong(in->firstSurface) + j;
		}
	}
}

/**
 * @brief CMod_LoadNodes
 * @param[in] l
 */
void CMod_LoadNodes(lump_t *l)
{
	dnode_t *in;
	int     child;
	cNode_t *out;
	int     i, j, count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadNodes: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	if (count < 1)
	{
		Com_Error(ERR_DROP, "CMod_LoadNodes: Map has no nodes");
	}
	cm.nodes    = Hunk_Alloc(count * sizeof(*cm.nodes), h_high);
	cm.numNodes = count;

	out = cm.nodes;

	for (i = 0 ; i < count ; i++, out++, in++)
	{
		out->plane = cm.planes + LittleLong(in->planeNum);
		for (j = 0 ; j < 2 ; j++)
		{
			child            = LittleLong(in->children[j]);
			out->children[j] = child;
		}
	}
}

/**
 * @brief CM_BoundBrush
 * @param[in] b
 */
void CM_BoundBrush(cbrush_t *b)
{
	b->bounds[0][0] = -b->sides[0].plane->dist;
	b->bounds[1][0] = b->sides[1].plane->dist;

	b->bounds[0][1] = -b->sides[2].plane->dist;
	b->bounds[1][1] = b->sides[3].plane->dist;

	b->bounds[0][2] = -b->sides[4].plane->dist;
	b->bounds[1][2] = b->sides[5].plane->dist;
}

/**
 * @brief CMod_LoadBrushes
 * @param[in] l
 */
void CMod_LoadBrushes(lump_t *l)
{
	dbrush_t *in;
	cbrush_t *out;
	int      i, count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadBrushes: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	cm.brushes    = Hunk_Alloc((BOX_BRUSHES + count) * sizeof(*cm.brushes), h_high);
	cm.numBrushes = count;

	out = cm.brushes;

	for (i = 0 ; i < count ; i++, out++, in++)
	{
		out->sides    = cm.brushsides + LittleLong(in->firstSide);
		out->numsides = LittleLong(in->numSides);

		out->shaderNum = LittleLong(in->shaderNum);
		if (out->shaderNum < 0 || out->shaderNum >= cm.numShaders)
		{
			Com_Error(ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum);
		}

		out->contents = cm.shaders[out->shaderNum].contentFlags;

		CM_BoundBrush(out);
	}
}

/**
 * @brief CMod_LoadLeafs
 * @param[in] l
 */
void CMod_LoadLeafs(lump_t *l)
{
	int     i;
	cLeaf_t *out;
	dleaf_t *in;
	int     count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadLeafs: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	if (count < 1)
	{
		Com_Error(ERR_DROP, "CMod_LoadLeafs: Map with no leafs");
	}

	cm.leafs    = Hunk_Alloc((BOX_LEAFS + count) * sizeof(*cm.leafs), h_high);
	cm.numLeafs = count;

	out = cm.leafs;
	for (i = 0 ; i < count ; i++, in++, out++)
	{
		out->cluster          = LittleLong(in->cluster);
		out->area             = LittleLong(in->area);
		out->firstLeafBrush   = LittleLong(in->firstLeafBrush);
		out->numLeafBrushes   = LittleLong(in->numLeafBrushes);
		out->firstLeafSurface = LittleLong(in->firstLeafSurface);
		out->numLeafSurfaces  = LittleLong(in->numLeafSurfaces);

		if (out->cluster >= cm.numClusters)
		{
			cm.numClusters = out->cluster + 1;
		}
		if (out->area >= cm.numAreas)
		{
			cm.numAreas = out->area + 1;
		}
	}

	cm.areas       = Hunk_Alloc(cm.numAreas * sizeof(*cm.areas), h_high);
	cm.areaPortals = Hunk_Alloc(cm.numAreas * cm.numAreas * sizeof(*cm.areaPortals), h_high);
}

/**
 * @brief CMod_LoadPlanes
 * @param l
 */
void CMod_LoadPlanes(lump_t *l)
{
	int      i, j;
	cplane_t *out;
	dplane_t *in;
	int      count;
	int      bits;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadPlanes: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	if (count < 1)
	{
		Com_Error(ERR_DROP, "CMod_LoadPlanes: Map with no planes");
	}
	cm.planes    = Hunk_Alloc((BOX_PLANES + count) * sizeof(*cm.planes), h_high);
	cm.numPlanes = count;

	out = cm.planes;

	for (i = 0 ; i < count ; i++, in++, out++)
	{
		bits = 0;
		for (j = 0 ; j < 3 ; j++)
		{
			out->normal[j] = LittleFloat(in->normal[j]);
			if (out->normal[j] < 0)
			{
				bits |= 1 << j;
			}
		}

		out->dist     = LittleFloat(in->dist);
		out->type     = PlaneTypeForNormal(out->normal);
		out->signbits = bits;
	}
}

/**
 * @brief CMod_LoadLeafBrushes
 * @param[in] l
 */
void CMod_LoadLeafBrushes(lump_t *l)
{
	int i;
	int *out;
	int *in;
	int count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadLeafBrushes: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	// ydnar: more than <count> brushes are stored in leafbrushes...
	cm.leafbrushes    = Hunk_Alloc((BOX_LEAF_BRUSHES + count) * sizeof(*cm.leafbrushes), h_high);
	cm.numLeafBrushes = count;

	out = cm.leafbrushes;

	for (i = 0 ; i < count ; i++, in++, out++)
	{
		*out = LittleLong(*in);
	}
}

/**
 * @brief CMod_LoadLeafSurfaces
 * @param[in] l
 */
void CMod_LoadLeafSurfaces(lump_t *l)
{
	int i;
	int *out;
	int *in;
	int count;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadLeafSurfaces: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	cm.leafsurfaces    = Hunk_Alloc(count * sizeof(*cm.leafsurfaces), h_high);
	cm.numLeafSurfaces = count;

	out = cm.leafsurfaces;

	for (i = 0 ; i < count ; i++, in++, out++)
	{
		*out = LittleLong(*in);
	}
}

/**
 * @brief CMod_LoadBrushSides
 * @param[in] l
 */
void CMod_LoadBrushSides(lump_t *l)
{
	int          i;
	cbrushside_t *out;
	dbrushside_t *in;
	int          count;
	int          num;

	in = ( void * )(cmod_base + l->fileofs);
	if (l->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadBrushSides: funny lump size");
	}
	count = l->filelen / sizeof(*in);

	cm.brushsides    = Hunk_Alloc((BOX_SIDES + count) * sizeof(*cm.brushsides), h_high);
	cm.numBrushSides = count;

	out = cm.brushsides;

	for (i = 0 ; i < count ; i++, in++, out++)
	{
		num            = LittleLong(in->planeNum);
		out->plane     = &cm.planes[num];
		out->shaderNum = LittleLong(in->shaderNum);
		if (out->shaderNum < 0 || out->shaderNum >= cm.numShaders)
		{
			Com_Error(ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum);
		}

		out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags;
	}
}

/**
 * @brief CMod_LoadCustomEntityString
 * @param[in] name
 */
qboolean CMod_LoadCustomEntityString(const char *name)
{
	fileHandle_t file;
	int          entFileLen = 0;
	char         *filename;
	char         noext[MAX_QPATH];

	COM_StripExtension(name, noext, sizeof(noext));
	filename = va("%s.ent", noext);

	entFileLen = FS_FOpenFileRead(filename, &file, qtrue);

	if (file && entFileLen > 0)
	{
		cm.entityString = (char *)Hunk_Alloc(entFileLen + 1, h_high);
		FS_Read(cm.entityString, entFileLen, file);
		FS_FCloseFile(file);
		cm.entityString[entFileLen] = '\0';
		Com_Printf("Loaded entities from %s\n", filename);
		return qtrue;
	}

	return qfalse;
}

/**
 * @brief CMod_LoadEntityString
 * @param[in] l
 * @param[in] name
 */
void CMod_LoadEntityString(lump_t *l, const char *name)
{
	if (CMod_LoadCustomEntityString(name))
	{
		return;
	}

	cm.entityString = Hunk_Alloc(l->filelen, h_high);
	//cm.numEntityChars = l->filelen;
	Com_Memcpy(cm.entityString, cmod_base + l->fileofs, l->filelen);
	Com_Printf("Loaded entities from %s\n", name);
}

#define VIS_HEADER  8

/**
 * @brief CMod_LoadVisibility
 * @param[in] l
 */
void CMod_LoadVisibility(lump_t *l)
{
	size_t len = l->filelen;
	byte   *buf;

	if (!len)
	{
		cm.clusterBytes = (cm.numClusters + 31) & ~31;
		cm.visibility   = Hunk_Alloc(cm.clusterBytes, h_high);
		Com_Memset(cm.visibility, 255, cm.clusterBytes);
		return;
	}

	buf = cmod_base + l->fileofs;

	cm.vised        = qtrue;
	cm.visibility   = Hunk_Alloc(len, h_high);
	cm.numClusters  = LittleLong(((int *)buf)[0]);
	cm.clusterBytes = LittleLong(((int *)buf)[1]);
	Com_Memcpy(cm.visibility, buf + VIS_HEADER, len - VIS_HEADER);
}

//==================================================================

#define MAX_PATCH_VERTS     1024

/**
 * @brief CMod_LoadPatches
 * @param[in] surfs
 * @param[in] verts
 */
void CMod_LoadPatches(lump_t *surfs, lump_t *verts)
{
	drawVert_t *dv, *dv_p;
	dsurface_t *in;
	int        count;
	int        i, j;
	int        c;
	cPatch_t   *patch;
	vec3_t     points[MAX_PATCH_VERTS];
	int        width, height;
	int        shaderNum;

	in = ( void * )(cmod_base + surfs->fileofs);
	if (surfs->filelen % sizeof(*in))
	{
		Com_Error(ERR_DROP, "CMod_LoadPatches: funny lump size");
	}
	cm.numSurfaces = count = surfs->filelen / sizeof(*in);
	cm.surfaces    = Hunk_Alloc(cm.numSurfaces * sizeof(cm.surfaces[0]), h_high);

	dv = ( void * )(cmod_base + verts->fileofs);
	if (verts->filelen % sizeof(*dv))
	{
		Com_Error(ERR_DROP, "CMod_LoadPatches: funny lump size");
	}

	// scan through all the surfaces, but only load patches,
	// not planar faces
	for (i = 0 ; i < count ; i++, in++)
	{
		if (LittleLong(in->surfaceType) != MST_PATCH)
		{
			continue;       // ignore other surfaces
		}
		// FIXME: check for non-colliding patches

		cm.surfaces[i] = patch = Hunk_Alloc(sizeof(*patch), h_high);

		// load the full drawverts onto the stack
		width  = LittleLong(in->patchWidth);
		height = LittleLong(in->patchHeight);
		c      = width * height;
		if (c > MAX_PATCH_VERTS)
		{
			Com_Error(ERR_DROP, "CMod_LoadPatches: ParseMesh - MAX_PATCH_VERTS");
		}

		dv_p = dv + LittleLong(in->firstVert);
		for (j = 0 ; j < c ; j++, dv_p++)
		{
			points[j][0] = LittleFloat(dv_p->xyz[0]);
			points[j][1] = LittleFloat(dv_p->xyz[1]);
			points[j][2] = LittleFloat(dv_p->xyz[2]);
		}

		shaderNum           = LittleLong(in->shaderNum);
		patch->contents     = cm.shaders[shaderNum].contentFlags;
		patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags;
		patch->shaderNum    = shaderNum;

		// create the internal facet structure
		patch->pc = CM_GeneratePatchCollide(width, height, points, qtrue);
	}
}

//==================================================================

/**
 * @brief CM_LumpChecksum
 * @param[in] lump
 */
unsigned CM_LumpChecksum(lump_t *lump)
{
	return LittleLong(Com_BlockChecksum(cmod_base + lump->fileofs, lump->filelen));
}

/**
 * @brief CM_Checksum
 * @param[in] header
 *
 * @note Unused
 */
unsigned CM_Checksum(dheader_t *header)
{
	unsigned checksums[16];

	checksums[0]  = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]);
	checksums[1]  = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]);
	checksums[2]  = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]);
	checksums[3]  = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]);
	checksums[4]  = CM_LumpChecksum(&header->lumps[LUMP_PLANES]);
	checksums[5]  = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]);
	checksums[6]  = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]);
	checksums[7]  = CM_LumpChecksum(&header->lumps[LUMP_MODELS]);
	checksums[8]  = CM_LumpChecksum(&header->lumps[LUMP_NODES]);
	checksums[9]  = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]);
	checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]);

	return LittleLong(Com_BlockChecksum(checksums, 11 * 4));
}

/**
 * @brief Loads in the map and all submodels
 * @param[in] name
 * @param[in] clientload
 * @param[out] checksum
 */
void CM_LoadMap(const char *name, qboolean clientload, unsigned int *checksum)
{
	union
	{
		int *i;
		void *v;
	} buf;
	unsigned int    i;
	dheader_t       header;
	int             length;
	static unsigned last_checksum;

	if (!name || !name[0])
	{
		Com_Error(ERR_DROP, "CM_LoadMap: NULL name");
	}

	cm_noAreas         = Cvar_Get("cm_noAreas", "0", CVAR_CHEAT);
	cm_noCurves        = Cvar_Get("cm_noCurves", "0", CVAR_CHEAT);
	cm_playerCurveClip = Cvar_Get("cm_playerCurveClip", "1", CVAR_ARCHIVE_ND | CVAR_CHEAT);
	cm_optimize        = Cvar_Get("cm_optimize", "1", CVAR_CHEAT);

	// pure client and not self hosted (to avoid mixing flags on local play)
	if (clientload && !com_sv_running->integer)
	{
		cm_optimizePatchPlanes = Cvar_Get("cm_optimizePatchPlanes", "0", CVAR_ROM | CVAR_SYSTEMINFO);
	}
	else
	{
		cm_optimizePatchPlanes = Cvar_Get("cm_optimizePatchPlanes", "0", CVAR_LATCH | CVAR_SYSTEMINFO);
	}

	Com_DPrintf("CM_LoadMap( %s, %i )\n", name, clientload);

	if (!strcmp(cm.name, name) && clientload)
	{
		*checksum = last_checksum;
		return;
	}

	// free old stuff
	Com_Memset(&cm, 0, sizeof(cm));
	CM_ClearLevelPatches();

	if (!name[0])
	{
		cm.numLeafs    = 1;
		cm.numClusters = 1;
		cm.numAreas    = 1;
		cm.cmodels     = Hunk_Alloc(sizeof(*cm.cmodels), h_high);
		*checksum      = 0;
		return;
	}

	// load the file
	length = FS_ReadFile(name, &buf.v);

	if (!buf.i || length <= 0)
	{
		Com_Error(ERR_DROP, "CM_LoadMap: Couldn't load %s", name);
	}

	last_checksum = LittleLong(Com_BlockChecksum(buf.i, length));
	*checksum     = last_checksum;

	header = *(dheader_t *)buf.i;
	for (i = 0 ; i < sizeof(dheader_t) / 4 ; i++)
	{
		((int *)&header)[i] = LittleLong(((int *)&header)[i]);
	}

	if (header.version != BSP_VERSION)
	{
		Com_Error(ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)"
		          , name, header.version, BSP_VERSION);
	}

	cmod_base = (byte *)buf.i;

	// load into heap
	CMod_LoadShaders(&header.lumps[LUMP_SHADERS]);
	CMod_LoadLeafs(&header.lumps[LUMP_LEAFS]);
	CMod_LoadLeafBrushes(&header.lumps[LUMP_LEAFBRUSHES]);
	CMod_LoadLeafSurfaces(&header.lumps[LUMP_LEAFSURFACES]);
	CMod_LoadPlanes(&header.lumps[LUMP_PLANES]);
	CMod_LoadBrushSides(&header.lumps[LUMP_BRUSHSIDES]);
	CMod_LoadBrushes(&header.lumps[LUMP_BRUSHES]);
	CMod_LoadSubmodels(&header.lumps[LUMP_MODELS]);
	CMod_LoadNodes(&header.lumps[LUMP_NODES]);
	CMod_LoadEntityString(&header.lumps[LUMP_ENTITIES], name);
	CMod_LoadVisibility(&header.lumps[LUMP_VISIBILITY]);
	CMod_LoadPatches(&header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS]);

	// we are NOT freeing the file, because it is cached for the ref
	FS_FreeFile(buf.v);

	CM_InitBoxHull();

	CM_FloodAreaConnections();

	// allow this to be cached if it is loaded by the server
	if (!clientload)
	{
		Q_strncpyz(cm.name, name, sizeof(cm.name));
	}
}

/**
 * @brief CM_ClearMap
 */
void CM_ClearMap(void)
{
	Com_Memset(&cm, 0, sizeof(cm));
	CM_ClearLevelPatches();
}

/**
 * @brief CM_ClipHandleToModel
 * @param[in] handle
 * @return
 */
cmodel_t *CM_ClipHandleToModel(clipHandle_t handle)
{
	if (handle < 0)
	{
		Com_Error(ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle);
	}
	if (handle < cm.numSubModels)
	{
		return &cm.cmodels[handle];
	}
	if (handle == BOX_MODEL_HANDLE || handle == CAPSULE_MODEL_HANDLE)
	{
		return &box_model;
	}
	if (handle < MAX_SUBMODELS)
	{
		Com_Error(ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i",
		          cm.numSubModels, handle, MAX_SUBMODELS);
	}

	Com_Error(ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS);

	return NULL;
}

/**
 * @brief CM_InlineModel
 * @param[in] index
 * @return
 */
clipHandle_t CM_InlineModel(int index)
{
	if (index < 0 || index >= cm.numSubModels)
	{
		Com_Error(ERR_DROP, "CM_InlineModel: bad number");
	}

	return index;
}

/**
 * @brief CM_NumClusters
 * @return
 */
int CM_NumClusters(void)
{
	return cm.numClusters;
}

/**
 * @brief CM_NumInlineModels
 * @return
 */
int CM_NumInlineModels(void)
{
	return cm.numSubModels;
}

/**
 * @brief CM_EntityString
 * @return
 */
char *CM_EntityString(void)
{
	return cm.entityString;
}

/**
 * @brief CM_LeafCluster
 * @param[in] leafnum
 * @return
 */
int CM_LeafCluster(int leafnum)
{
	if (leafnum < 0 || leafnum >= cm.numLeafs)
	{
		Com_Error(ERR_DROP, "CM_LeafCluster: bad number");
	}

	return cm.leafs[leafnum].cluster;
}

/**
 * @brief CM_LeafArea
 * @param[in] leafnum
 * @return
 */
int CM_LeafArea(int leafnum)
{
	if (leafnum < 0 || leafnum >= cm.numLeafs)
	{
		Com_Error(ERR_DROP, "CM_LeafArea: bad number");
	}

	return cm.leafs[leafnum].area;
}

//=======================================================================

/**
 * @brief Set up the planes and nodes so that the six floats of a bounding box
 * can just be stored out and get a proper clipping hull structure.
 */
void CM_InitBoxHull(void)
{
	byte         i;
	int          side;
	cplane_t     *p;
	cbrushside_t *s;

	box_planes = &cm.planes[cm.numPlanes];

	box_brush           = &cm.brushes[cm.numBrushes];
	box_brush->numsides = 6;
	box_brush->sides    = cm.brushsides + cm.numBrushSides;
	box_brush->contents = CONTENTS_BODY;

	box_model.leaf.numLeafBrushes = 1;
	//box_model.leaf.firstLeafBrush = cm.numBrushes;
	box_model.leaf.firstLeafBrush     = cm.numLeafBrushes;
	cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes;

	for (i = 0 ; i < 6 ; i++)
	{
		side = i & 1;

		// brush sides
		s               = &cm.brushsides[cm.numBrushSides + i];
		s->plane        = cm.planes + (cm.numPlanes + i * 2 + side);
		s->surfaceFlags = 0;

		// planes
		p           = &box_planes[i * 2];
		p->type     = i >> 1;
		p->signbits = 0;
		VectorClear(p->normal);
		p->normal[i >> 1] = 1;

		p           = &box_planes[i * 2 + 1];
		p->type     = 3 + (i >> 1);
		p->signbits = 0;
		VectorClear(p->normal);
		p->normal[i >> 1] = -1;

		SetPlaneSignbits(p);
	}
}

/**
 * @brief To keep everything totally uniform, bounding boxes are turned into small
 * BSP trees instead of being compared directly.
 * Capsules are handled differently though.
 *
 * @param[in] mins
 * @param[in] maxs
 * @param[in] capsule
 * @return
 */
clipHandle_t CM_TempBoxModel(const vec3_t mins, const vec3_t maxs, qboolean capsule)
{
	VectorCopy(mins, box_model.mins);
	VectorCopy(maxs, box_model.maxs);

	if (capsule)
	{
		return CAPSULE_MODEL_HANDLE;
	}

	box_planes[0].dist  = maxs[0];
	box_planes[1].dist  = -maxs[0];
	box_planes[2].dist  = mins[0];
	box_planes[3].dist  = -mins[0];
	box_planes[4].dist  = maxs[1];
	box_planes[5].dist  = -maxs[1];
	box_planes[6].dist  = mins[1];
	box_planes[7].dist  = -mins[1];
	box_planes[8].dist  = maxs[2];
	box_planes[9].dist  = -maxs[2];
	box_planes[10].dist = mins[2];
	box_planes[11].dist = -mins[2];

	VectorCopy(mins, box_brush->bounds[0]);
	VectorCopy(maxs, box_brush->bounds[1]);

	return BOX_MODEL_HANDLE;
}

/**
 * @brief CM_SetTempBoxModelContents
 * @param[in] contents
 */
void CM_SetTempBoxModelContents(int contents)
{
	box_brush->contents = contents;
}

/**
 * @brief CM_ModelBounds
 * @param[in] model
 * @param[out] mins
 * @param[out] maxs
 */
void CM_ModelBounds(clipHandle_t model, vec3_t mins, vec3_t maxs)
{
	cmodel_t *cmod;

	cmod = CM_ClipHandleToModel(model);
	VectorCopy(cmod->mins, mins);
	VectorCopy(cmod->maxs, maxs);
}
