/*
 * Copyright (C) Freespace Open 2015.  All rights reserved.
 *
 * All source code herein is the property of Freespace Open. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/

#include "render/batching.h"
#include "globalincs/pstypes.h"
#include "globalincs/systemvars.h"
#include "graphics/2d.h"
#include "render/3d.h"
#include "graphics/material.h"
#include "tracing/tracing.h"

static SCP_map<batch_info, primitive_batch> Batching_primitives;
static SCP_map<batch_buffer_key, primitive_batch_buffer> Batching_buffers;
static int lineTexture = -1;

void primitive_batch::add_triangle(batch_vertex* v0, batch_vertex* v1, batch_vertex *v2)
{
	Vertices.push_back(*v0);
	Vertices.push_back(*v1);
	Vertices.push_back(*v2);
}

void primitive_batch::add_point_sprite(batch_vertex *p)
{
	Vertices.push_back(*p);
}

size_t primitive_batch::load_buffer(batch_vertex* buffer, size_t n_verts)
{
	size_t verts_to_render = Vertices.size();

	for ( size_t i = 0; i < verts_to_render; ++i) {
		buffer[n_verts+i] = Vertices[i];
	}

	return verts_to_render;
}

void primitive_batch::clear()
{
	Vertices.clear();
}

void batching_setup_vertex_layout(vertex_layout *layout, uint vert_mask)
{
	int stride = sizeof(batch_vertex);

	if ( vert_mask & vertex_format_data::mask(vertex_format_data::POSITION3) ) {
		layout->add_vertex_component(vertex_format_data::POSITION3, stride, (int)offsetof(batch_vertex, position));
	}

	if ( vert_mask & vertex_format_data::mask(vertex_format_data::TEX_COORD4) ) {
		layout->add_vertex_component(vertex_format_data::TEX_COORD4, stride, (int)offsetof(batch_vertex, tex_coord));
	}

	if ( vert_mask & vertex_format_data::mask(vertex_format_data::COLOR4) ) {
		layout->add_vertex_component(vertex_format_data::COLOR4, stride, (int)offsetof(batch_vertex, r));
	}

	if ( vert_mask & vertex_format_data::mask(vertex_format_data::RADIUS) ) {
		layout->add_vertex_component(vertex_format_data::RADIUS, stride, (int)offsetof(batch_vertex, radius));
	}

	if ( vert_mask & vertex_format_data::mask(vertex_format_data::UVEC) ) {
		layout->add_vertex_component(vertex_format_data::UVEC, stride, (int)offsetof(batch_vertex, uvec));
	}
}

void batching_init_buffer(primitive_batch_buffer *buffer, primitive_type prim_type, uint vertex_mask)
{
	batching_setup_vertex_layout(&buffer->layout, vertex_mask);

	buffer->buffer_num = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Streaming);
	buffer->buffer_ptr = NULL;
	buffer->buffer_size = 0;
	buffer->desired_buffer_size = 0;
	buffer->prim_type = prim_type;
}

void batching_determine_blend_color(color *clr, int texture, float alpha)
{
	gr_alpha_blend blend_mode = material_determine_blend_mode(texture, true);

	if ( blend_mode == ALPHA_BLEND_ADDITIVE ) {
		gr_init_alphacolor(clr, fl2i(255.0f*alpha), fl2i(255.0f*alpha), fl2i(255.0f*alpha), 255);
	} else {
		gr_init_alphacolor(clr, 255, 255, 255, fl2i(255.0f*alpha));
	}
}

primitive_batch_buffer* batching_find_buffer(uint vertex_mask, primitive_type prim_type)
{
	batch_buffer_key query(vertex_mask, prim_type);

	SCP_map<batch_buffer_key, primitive_batch_buffer>::iterator iter = Batching_buffers.find(query);

	if ( iter == Batching_buffers.end() ) {
		primitive_batch_buffer *buffer = &Batching_buffers[query];

		batching_init_buffer(buffer, prim_type, vertex_mask);

		return buffer;
	} else {
		return &iter->second;
	}
}

primitive_batch* batching_find_batch(int texture, batch_info::material_type material_id, primitive_type prim_type, bool thruster)
{
	// Use the base texture for finding the batch item since all items can reuse the same texture array
	auto base_tex = bm_get_base_frame(texture);

	batch_info query(material_id, base_tex, prim_type, thruster);

	SCP_map<batch_info, primitive_batch>::iterator iter = Batching_primitives.find(query);

	if ( iter == Batching_primitives.end() ) {
		primitive_batch* batch = &Batching_primitives[query];

		*batch = primitive_batch(query);

		return batch;
	} else {
		return &iter->second;
	}
}

uint batching_determine_vertex_layout(batch_info *info)
{
	if ( info->prim_type == PRIM_TYPE_POINTS ) {
		return vertex_format_data::mask(vertex_format_data::POSITION3) 
			| vertex_format_data::mask(vertex_format_data::RADIUS) 
			| vertex_format_data::mask(vertex_format_data::UVEC)
			| vertex_format_data::mask(vertex_format_data::TEX_COORD4);
	}

	if ( info->mat_type == batch_info::VOLUME_EMISSIVE || info->mat_type == batch_info::DISTORTION ) {
		return vertex_format_data::mask(vertex_format_data::POSITION3) 
			| vertex_format_data::mask(vertex_format_data::COLOR4) 
			| vertex_format_data::mask(vertex_format_data::TEX_COORD4)
			| vertex_format_data::mask(vertex_format_data::RADIUS);
	} else {
		return vertex_format_data::mask(vertex_format_data::POSITION3) 
			| vertex_format_data::mask(vertex_format_data::COLOR4) 
			| vertex_format_data::mask(vertex_format_data::TEX_COORD4);
	}
}

void batching_add_bitmap_internal(primitive_batch *batch, int texture, vertex *pnt, int orient, float rad, color *clr, float depth)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	float radius = rad;
	rad *= 1.41421356f;//1/0.707, becase these are the points of a square or width and height rad

	vec3d PNT(pnt->world);
	vec3d p[4];
	vec3d fvec, rvec, uvec;
	batch_vertex verts[6];

	// get the direction from the point to the eye
	vm_vec_sub(&fvec, &View_position, &PNT);
	vm_vec_normalize_safe(&fvec);

	// get an up vector in the general direction of what we want
	uvec = View_matrix.vec.uvec;

	// make a right vector from the f and up vector, this r vec is exactly what we want, so...
	vm_vec_cross(&rvec, &View_matrix.vec.fvec, &uvec);
	vm_vec_normalize_safe(&rvec);

	// fix the u vec with it
	vm_vec_cross(&uvec, &View_matrix.vec.fvec, &rvec);

	// move the center of the sprite based on the depth parameter
	if ( depth != 0.0f )
		vm_vec_scale_add(&PNT, &PNT, &fvec, depth);

	// move one of the verts to the left
	vm_vec_scale_add(&p[0], &PNT, &rvec, rad);

	// and one to the right
	vm_vec_scale_add(&p[2], &PNT, &rvec, -rad);

	// now move all oof the verts to were they need to be
	vm_vec_scale_add(&p[1], &p[2], &uvec, rad);
	vm_vec_scale_add(&p[3], &p[0], &uvec, -rad);
	vm_vec_scale_add(&p[0], &p[0], &uvec, rad);
	vm_vec_scale_add(&p[2], &p[2], &uvec, -rad);

	//move all the data from the vecs into the verts
	//tri 1
	verts[5].position = p[3];
	verts[4].position = p[2];
	verts[3].position = p[1];

	//tri 2
	verts[2].position = p[3];
	verts[1].position = p[1];
	verts[0].position = p[0];

	// set up the UV coords
	if ( orient & 1 ) {
		// tri 1
		verts[5].tex_coord.xyzw.x = 1.0f;
		verts[4].tex_coord.xyzw.x = 0.0f;
		verts[3].tex_coord.xyzw.x = 0.0f;

		// tri 2
		verts[2].tex_coord.xyzw.x = 1.0f;
		verts[1].tex_coord.xyzw.x = 0.0f;
		verts[0].tex_coord.xyzw.x = 1.0f;
	} else {
		// tri 1
		verts[5].tex_coord.xyzw.x = 0.0f;
		verts[4].tex_coord.xyzw.x = 1.0f;
		verts[3].tex_coord.xyzw.x = 1.0f;

		// tri 2
		verts[2].tex_coord.xyzw.x = 0.0f;
		verts[1].tex_coord.xyzw.x = 1.0f;
		verts[0].tex_coord.xyzw.x = 0.0f;
	}

	if ( orient & 2 ) {
		// tri 1
		verts[5].tex_coord.xyzw.y = 1.0f;
		verts[4].tex_coord.xyzw.y = 1.0f;
		verts[3].tex_coord.xyzw.y = 0.0f;

		// tri 2
		verts[2].tex_coord.xyzw.y = 1.0f;
		verts[1].tex_coord.xyzw.y = 0.0f;
		verts[0].tex_coord.xyzw.y = 0.0f;
	} else {
		// tri 1
		verts[5].tex_coord.xyzw.y = 0.0f;
		verts[4].tex_coord.xyzw.y = 0.0f;
		verts[3].tex_coord.xyzw.y = 1.0f;

		// tri 2
		verts[2].tex_coord.xyzw.y = 0.0f;
		verts[1].tex_coord.xyzw.y = 1.0f;
		verts[0].tex_coord.xyzw.y = 1.0f;
	}

	auto array_index = texture - batch->get_render_info().texture;
	for (int i = 0; i < 6 ; i++) {
		verts[i].r = clr->red;
		verts[i].g = clr->green;
		verts[i].b = clr->blue;
		verts[i].a = clr->alpha;

		verts[i].radius = radius;

		// Set array index
		verts[i].tex_coord.xyzw.z = (float) array_index;
		verts[i].tex_coord.xyzw.w = 1.0f;
	}

	batch->add_triangle(&verts[5], &verts[4], &verts[3]);
	batch->add_triangle(&verts[2], &verts[1], &verts[0]);
}

void batching_add_bitmap_rotated_internal(primitive_batch *batch, int texture, vertex *pnt, float angle, float rad, color *clr, float depth)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	float radius = rad;
	rad *= 1.41421356f;//1/0.707, becase these are the points of a square or width and height rad

	extern float Physics_viewer_bank;
	angle -= Physics_viewer_bank;

	if ( angle < 0.0f )
		angle += PI2;
	else if ( angle > PI2 )
		angle -= PI2;

	vec3d PNT(pnt->world);
	vec3d p[4];
	vec3d fvec, rvec, uvec;
	batch_vertex verts[6];

	vm_vec_sub(&fvec, &View_position, &PNT);
	vm_vec_normalize_safe(&fvec);

	vm_rot_point_around_line(&uvec, &View_matrix.vec.uvec, angle, &vmd_zero_vector, &View_matrix.vec.fvec);

	vm_vec_cross(&rvec, &View_matrix.vec.fvec, &uvec);
	vm_vec_normalize_safe(&rvec);
	vm_vec_cross(&uvec, &View_matrix.vec.fvec, &rvec);

	vm_vec_scale_add(&PNT, &PNT, &fvec, depth);
	vm_vec_scale_add(&p[0], &PNT, &rvec, rad);
	vm_vec_scale_add(&p[2], &PNT, &rvec, -rad);

	vm_vec_scale_add(&p[1], &p[2], &uvec, rad);
	vm_vec_scale_add(&p[3], &p[0], &uvec, -rad);
	vm_vec_scale_add(&p[0], &p[0], &uvec, rad);
	vm_vec_scale_add(&p[2], &p[2], &uvec, -rad);


	//move all the data from the vecs into the verts
	//tri 1
	verts[5].position = p[3];
	verts[4].position = p[2];
	verts[3].position = p[1];

	//tri 2
	verts[2].position = p[3];
	verts[1].position = p[1];
	verts[0].position = p[0];

	//tri 1
	verts[5].tex_coord.xyzw.x = 0.0f;	verts[5].tex_coord.xyzw.y = 0.0f;
	verts[4].tex_coord.xyzw.x = 1.0f;	verts[4].tex_coord.xyzw.y = 0.0f;
	verts[3].tex_coord.xyzw.x = 1.0f;	verts[3].tex_coord.xyzw.y = 1.0f;

	//tri 2
	verts[2].tex_coord.xyzw.x = 0.0f;	verts[2].tex_coord.xyzw.y = 0.0f;
	verts[1].tex_coord.xyzw.x = 1.0f;	verts[1].tex_coord.xyzw.y = 1.0f;
	verts[0].tex_coord.xyzw.x = 0.0f;	verts[0].tex_coord.xyzw.y = 1.0f;

	auto array_index = texture - batch->get_render_info().texture;
	for (int i = 0; i < 6 ; i++) {
		verts[i].r = clr->red;
		verts[i].g = clr->green;
		verts[i].b = clr->blue;
		verts[i].a = clr->alpha;

		verts[i].radius = radius;
		verts[i].tex_coord.xyzw.z = (float)array_index;
		verts[i].tex_coord.xyzw.w = 1.0f;
	}

	batch->add_triangle(&verts[0], &verts[1], &verts[2]);
	batch->add_triangle(&verts[3], &verts[4], &verts[5]);
}

void batching_add_polygon_internal(primitive_batch *batch, int texture, const vec3d *pos, const matrix *orient, float width, float height, color *clr)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	//idiot-proof
	if(width == 0 || height == 0)
		return;

	Assert(pos != NULL);
	Assert(orient != NULL);

	//Let's begin.

	const int NUM_VERTICES = 4;
	vec3d p[NUM_VERTICES] = { ZERO_VECTOR };
	batch_vertex v[NUM_VERTICES];

	p[0].xyz.x = width;
	p[0].xyz.y = height;

	p[1].xyz.x = -width;
	p[1].xyz.y = height;

	p[2].xyz.x = -width;
	p[2].xyz.y = -height;

	p[3].xyz.x = width;
	p[3].xyz.y = -height;

	auto array_index = texture - batch->get_render_info().texture;

	for(int i = 0; i < NUM_VERTICES; i++)
	{
		vec3d tmp = vmd_zero_vector;

		//Rotate correctly
		vm_vec_unrotate(&tmp, &p[i], orient);
		//Move to point in space
		vm_vec_add2(&tmp, pos);

		v[i].position = tmp;

		v[i].r = clr->red;
		v[i].g = clr->green;
		v[i].b = clr->blue;
		v[i].a = clr->alpha;
		v[i].tex_coord.xyzw.z = (float)array_index;
		v[i].tex_coord.xyzw.w = 1.0f;

		v[i].radius = MAX(width * 0.5f, height * 0.5f);
	}

	v[0].tex_coord.xyzw.x = 1.0f;
	v[0].tex_coord.xyzw.y = 0.0f;

	v[1].tex_coord.xyzw.x = 0.0f;
	v[1].tex_coord.xyzw.y = 0.0f;

	v[2].tex_coord.xyzw.x = 0.0f;
	v[2].tex_coord.xyzw.y = 1.0f;

	v[3].tex_coord.xyzw.x = 1.0f;
	v[3].tex_coord.xyzw.y = 1.0f;

	batch->add_triangle(&v[0], &v[1], &v[2]);
	batch->add_triangle(&v[0], &v[2], &v[3]);
}

void batching_add_quad_internal(primitive_batch* batch, int texture, vertex* verts, float trapezoidal_correction = 1.0f)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	const int NUM_VERTICES = 4;
	batch_vertex v[NUM_VERTICES];

	auto array_index = texture - batch->get_render_info().texture;

	v[0].tex_coord = vm_vec4_new(verts[0].texture_position.u, verts[0].texture_position.v, (float)array_index, 1.0f);
	v[1].tex_coord = vm_vec4_new(verts[1].texture_position.u, verts[1].texture_position.v, (float)array_index, 1.0f);
	v[2].tex_coord = vm_vec4_new(verts[2].texture_position.u, verts[2].texture_position.v * trapezoidal_correction, (float)array_index, trapezoidal_correction);
	v[3].tex_coord = vm_vec4_new(verts[3].texture_position.u, verts[3].texture_position.v, (float)array_index, trapezoidal_correction);

	for ( int i = 0; i < NUM_VERTICES; i++ ) {
		v[i].position = verts[i].world;
		
		v[i].r = verts[i].r;
		v[i].g = verts[i].g;
		v[i].b = verts[i].b;
		v[i].a = verts[i].a;
	}

	batch->add_triangle(&v[0], &v[1], &v[2]);
	batch->add_triangle(&v[0], &v[2], &v[3]);
}

void batching_add_tri_internal(primitive_batch *batch, int texture, vertex *verts)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	const int NUM_VERTICES = 3;
	batch_vertex v[NUM_VERTICES];

	auto array_index = texture - batch->get_render_info().texture;
	for ( int i = 0; i < NUM_VERTICES; i++ ) {
		v[i].position = verts[i].world;

		v[i].r = verts[i].r;
		v[i].g = verts[i].g;
		v[i].b = verts[i].b;
		v[i].a = verts[i].a;

		v[i].tex_coord.xyzw.x = verts[i].texture_position.u;
		v[i].tex_coord.xyzw.y = verts[i].texture_position.v;
		v[i].tex_coord.xyzw.z = (float)array_index;
		v[i].tex_coord.xyzw.w = 1.0f;
	}

	batch->add_triangle(&v[0], &v[1], &v[2]);
}

void batching_add_beam_internal(primitive_batch *batch, int texture, vec3d *start, vec3d *end, float width, color *clr, float offset)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	vec3d p[4];
	batch_vertex verts[6];

	vec3d fvec, uvecs, uvece, evec;

	vm_vec_sub(&fvec, start, end);
	vm_vec_normalize_safe(&fvec);

	vm_vec_sub(&evec, &View_position, start);
	vm_vec_normalize_safe(&evec);

	vm_vec_cross(&uvecs, &fvec, &evec);
	vm_vec_normalize_safe(&uvecs);

	vm_vec_sub(&evec, &View_position, end);
	vm_vec_normalize_safe(&evec);

	vm_vec_cross(&uvece, &fvec, &evec);
	vm_vec_normalize_safe(&uvece);

	vm_vec_scale_add(&p[0], start, &uvecs, width);
	vm_vec_scale_add(&p[1], end, &uvece, width);
	vm_vec_scale_add(&p[2], end, &uvece, -width);
	vm_vec_scale_add(&p[3], start, &uvecs, -width);

	//move all the data from the vecs into the verts
	//tri 1
	verts[0].position = p[3];
	verts[1].position = p[2];
	verts[2].position = p[1];

	//tri 2
	verts[3].position = p[3];
	verts[4].position = p[1];
	verts[5].position = p[0];

	//set up the UV coords
	//tri 1
	verts[0].tex_coord.xyzw.x = 0.0f; verts[0].tex_coord.xyzw.y = 0.0f;
	verts[1].tex_coord.xyzw.x = 1.0f; verts[1].tex_coord.xyzw.y = 0.0f;
	verts[2].tex_coord.xyzw.x = 1.0f; verts[2].tex_coord.xyzw.y = 1.0f;

	//tri 2
	verts[3].tex_coord.xyzw.x = 0.0f; verts[3].tex_coord.xyzw.y = 0.0f;
	verts[4].tex_coord.xyzw.x = 1.0f; verts[4].tex_coord.xyzw.y = 1.0f;
	verts[5].tex_coord.xyzw.x = 0.0f; verts[5].tex_coord.xyzw.y = 1.0f;

	auto array_index = texture - batch->get_render_info().texture;
	for(int i = 0; i < 6; i++){
		verts[i].r = clr->red;
		verts[i].g = clr->green;
		verts[i].b = clr->blue;
		verts[i].a = clr->alpha;

		if(offset > 0.0f) {
			verts[i].radius = offset;
		} else {
			verts[i].radius = width;
		}
		verts[i].tex_coord.xyzw.z = (float)array_index;
		verts[i].tex_coord.xyzw.w = 1.0f;
	}

	batch->add_triangle(&verts[0], &verts[1], &verts[2]);
	batch->add_triangle(&verts[3], &verts[4], &verts[5]);
}

void batching_add_line_internal(primitive_batch *batch, int texture, vec3d *start, vec3d *end, float width1, float width2, color *clr)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	vec3d p[4];
	batch_vertex verts[6];

	vec3d fvec, uvecs, uvece, evec;

	vm_vec_sub(&fvec, start, end);
	vm_vec_normalize_safe(&fvec);

	vm_vec_sub(&evec, &View_position, start);
	vm_vec_normalize_safe(&evec);

	vm_vec_cross(&uvecs, &fvec, &evec);
	vm_vec_normalize_safe(&uvecs);

	vm_vec_sub(&evec, &View_position, end);
	vm_vec_normalize_safe(&evec);

	vm_vec_cross(&uvece, &fvec, &evec);
	vm_vec_normalize_safe(&uvece);

	vm_vec_scale_add(&p[0], start, &uvecs, width1);
	vm_vec_scale_add(&p[1], end, &uvece, width2);
	vm_vec_scale_add(&p[2], end, &uvece, -width2);
	vm_vec_scale_add(&p[3], start, &uvecs, -width1);

	//move all the data from the vecs into the verts
	//tri 1
	verts[0].position = p[3];
	verts[1].position = p[2];
	verts[2].position = p[1];

	//tri 2
	verts[3].position = p[3];
	verts[4].position = p[1];
	verts[5].position = p[0];

	//set up the UV coords
	//tri 1
	verts[0].tex_coord.xyzw.x = 0.0f; verts[0].tex_coord.xyzw.y = 0.0f;
	verts[1].tex_coord.xyzw.x = 1.0f; verts[1].tex_coord.xyzw.y = 0.0f;
	verts[2].tex_coord.xyzw.x = 1.0f; verts[2].tex_coord.xyzw.y = 1.0f;

	//tri 2
	verts[3].tex_coord.xyzw.x = 0.0f; verts[3].tex_coord.xyzw.y = 0.0f;
	verts[4].tex_coord.xyzw.x = 1.0f; verts[4].tex_coord.xyzw.y = 1.0f;
	verts[5].tex_coord.xyzw.x = 0.0f; verts[5].tex_coord.xyzw.y = 1.0f;

	auto array_index = texture - batch->get_render_info().texture;
	for(batch_vertex &vert : verts){
		vert.r = clr->red;
		vert.g = clr->green;
		vert.b = clr->blue;
		vert.a = clr->alpha;

		vert.radius = width1;
		
		vert.tex_coord.xyzw.z = (float)array_index;
		vert.tex_coord.xyzw.w = 1.0f;
	}

	batch->add_triangle(&verts[0], &verts[1], &verts[2]);
	batch->add_triangle(&verts[3], &verts[4], &verts[5]);
}

void batching_add_laser_internal(primitive_batch *batch, int texture, const vec3d *p0, float width1, const vec3d *p1, float width2, int r, int g, int b)
{
	Assert(batch->get_render_info().prim_type == PRIM_TYPE_TRIS);

	width1 *= 0.5f;
	width2 *= 0.5f;

	vec3d uvec, fvec, rvec, center, reye;

	vm_vec_sub( &fvec, p0, p1 );
	vm_vec_normalize_safe( &fvec );

	vm_vec_avg( &center, p0, p1 ); // needed for the return value only
	vm_vec_sub(&reye, &Eye_position, &center);
	vm_vec_normalize(&reye);

	// compute the up vector
	vm_vec_cross(&uvec, &fvec, &reye);
	vm_vec_normalize_safe(&uvec);
	// ... the forward vector
	vm_vec_cross(&fvec, &uvec, &reye);
	vm_vec_normalize_safe(&fvec);
	// now recompute right vector, in case it wasn't entirely perpendiclar
	vm_vec_cross(&rvec, &uvec, &fvec);

	// Now have uvec, which is up vector and rvec which is the normal
	// of the face.

	vec3d start, end;

	vm_vec_scale_add(&start, p0, &fvec, -width1);
	vm_vec_scale_add(&end, p1, &fvec, width2);

	vec3d vecs[4];
	batch_vertex verts[6];

	vm_vec_scale_add( &vecs[0], &end, &uvec, width2 );
	vm_vec_scale_add( &vecs[1], &start, &uvec, width1 );
	vm_vec_scale_add( &vecs[2], &start, &uvec, -width1 );
	vm_vec_scale_add( &vecs[3], &end, &uvec, -width2 );

	verts[0].position = vecs[0];
	verts[1].position = vecs[1];
	verts[2].position = vecs[2];

	verts[3].position = vecs[0];
	verts[4].position = vecs[2];
	verts[5].position = vecs[3];

	auto array_index = texture - batch->get_render_info().texture;

	float ratio = width2 / width1;
	if (width1 <= 0.0f)
		ratio = 999.0f;

	verts[0].tex_coord = vm_vec4_new(1.0f, 0.0f, (float)array_index, ratio);
	verts[1].tex_coord = vm_vec4_new(0.0f, 0.0f, (float)array_index, 1.0f);
	verts[2].tex_coord = vm_vec4_new(0.0f, 1.0f, (float)array_index, 1.0f);
	verts[3].tex_coord = vm_vec4_new(1.0f, 0.0f, (float)array_index, ratio);
	verts[4].tex_coord = vm_vec4_new(0.0f, 1.0f, (float)array_index, 1.0f);
	verts[5].tex_coord = vm_vec4_new(1.0f, ratio, (float)array_index, ratio);

	for (auto& vert : verts) {
		vert.r = (ubyte)r;
		vert.g = (ubyte)g;
		vert.b = (ubyte)b;
		vert.a = 255;
	}

	batch->add_triangle(&verts[0], &verts[1], &verts[2]);
	batch->add_triangle(&verts[3], &verts[4], &verts[5]);
}

void batching_add_bitmap(int texture, vertex *pnt, int orient, float rad, float alpha, float depth)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_bitmap_internal(batch, texture, pnt, orient, rad, &clr, depth);
}

void batching_add_volume_bitmap(int texture, vertex *pnt, int orient, float rad, float alpha, float depth)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch;
	
	if ( gr_is_capable(gr_capability::CAPABILITY_SOFT_PARTICLES) ) {
		batch = batching_find_batch(texture, batch_info::VOLUME_EMISSIVE);
	} else {
		batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);
	}

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_bitmap_internal(batch, texture, pnt, orient, rad, &clr, depth);
}

void batching_add_volume_bitmap_rotated(int texture, vertex *pnt, float angle, float rad, float alpha, float depth)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch;

	if ( gr_is_capable(gr_capability::CAPABILITY_SOFT_PARTICLES) ) {
		batch = batching_find_batch(texture, batch_info::VOLUME_EMISSIVE);
	} else {
		batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);
	}

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_bitmap_rotated_internal(batch, texture, pnt, angle, rad, &clr, depth);
}

void batching_add_distortion_bitmap_rotated(int texture, vertex *pnt, float angle, float rad, float alpha, float depth)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	if ( !gr_is_capable(gr_capability::CAPABILITY_DISTORTION) ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::DISTORTION);

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_bitmap_rotated_internal(batch, texture, pnt, angle, rad, &clr, depth);
}

void batching_add_distortion_beam(int texture, vec3d *start, vec3d *end, float width, float intensity, float offset)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	if ( !gr_is_capable(gr_capability::CAPABILITY_DISTORTION) ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::DISTORTION, PRIM_TYPE_TRIS, true);

	color clr;
	batching_determine_blend_color(&clr, texture, intensity);

	batching_add_beam_internal(batch, texture, start, end, width, &clr, offset);
}

void batching_add_beam(int texture, vec3d *start, vec3d *end, float width, float intensity)
{
	Assertion((texture >= 0), "batching_add_...() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	color clr;
	batching_determine_blend_color(&clr, texture, intensity);


	batching_add_beam_internal(batch, texture, start, end, width, &clr, 0.0f);
}

void batching_add_line(vec3d *start, vec3d *end, float widthStart, float widthEnd, color custom_color, bool translucent)
{
	color clr = custom_color;
	primitive_batch *batch;

	//The widths can't be negative. 
	if (widthStart < 0)
	{
		mprintf(("Warning: Neither end of a 3D line can have negative width. Attempted to draw a line with %f widthStart. Changing value to zero.\n", widthStart));
		widthStart = 0;
	}
	if (widthEnd < 0)
	{
		mprintf(("Warning: Neither end of a 3D line can have negative width. Attempted to draw a line with %f widthEnd. Changing value to zero.\n", widthEnd));
		widthEnd = 0;
	}

	//A line can't have zero width on both ends of it. (But it can have zero width on only one of the ends at a time)
	if (widthStart <= 0 && widthEnd <= 0)
	{
		mprintf(("Warning: The width of a 3D line should be > 0 on at least one tip. Attempted to draw a line with %f to %f. Line will not be drawn.\n", widthStart, widthEnd));
		return;
	}

	if (lineTexture < 0)
	{
		//We only need a single pixel sized texture to render as many lines as we want. 
		//If it doesn't exist yet, then we make one!
		auto previous_target = gr_screen.rendering_to_texture;

		//The texture needs to be colored white, otherwise the line will render
		//as black (or invisible if in translucent mode) no matter which color the user picks		
		lineTexture = bm_make_render_target(1, 1, BMP_FLAG_RENDER_TARGET_STATIC);
		bm_set_render_target(lineTexture);			
		color temp = gr_screen.current_color; 			//Store our working color
		color c;
		gr_init_color(&c, 255, 255, 255);
		gr_set_color_fast(&c); 							//set white as the working color
		gr_pixel(0, 0, GR_RESIZE_NONE);					//paint the pixel to the texture
		gr_set_color_fast(&temp);						//Reset our working color
		bm_set_render_target(previous_target);			//Reset our render target
	}

	if (translucent){
		batch = batching_find_batch(lineTexture, batch_info::FLAT_EMISSIVE);
	}
	else {
		batch = batching_find_batch(lineTexture, batch_info::FLAT_OPAQUE);
	}
	
	batching_add_line_internal(batch, lineTexture, start, end, widthStart, widthEnd, &clr);
}

void batching_add_laser(int texture, const vec3d *p0, float width1, const vec3d *p1, float width2, int r, int g, int b)
{
	Assertion((texture >= 0), "batching_add_laser() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	batching_add_laser_internal(batch, texture, p0, width1, p1, width2, r, g, b);
}

void batching_add_volume_polygon(int texture, const vec3d* pos, const matrix* orient, float width, float height, float alpha)
{
	Assertion((texture >= 0), "batching_add_volume_polygon() attempted for invalid texture");
	if (texture < 0) {
		return;
	}

	primitive_batch* batch;

	if (gr_is_capable(gr_capability::CAPABILITY_SOFT_PARTICLES)) {
		batch = batching_find_batch(texture, batch_info::VOLUME_EMISSIVE);
	} else {
		batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);
	}

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_polygon_internal(batch, texture, pos, orient, width, height, &clr);
}

void batching_add_polygon(int texture, vec3d *pos, matrix *orient, float width, float height, float alpha)
{
	Assertion((texture >= 0), "batching_add_polygon() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	color clr;
	batching_determine_blend_color(&clr, texture, alpha);

	batching_add_polygon_internal(batch, texture, pos, orient, width, height, &clr);
}

void batching_add_quad(int texture, vertex *verts, float trapezoidal_correction)
{
	Assertion((texture >= 0), "batching_add_quad() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	batching_add_quad_internal(batch, texture, verts, trapezoidal_correction);
}

void batching_add_quad(int texture, vertex *verts, primitive_batch *batch, float trapezoidal_correction)
{
	Assertion((texture >= 0), "batching_add_quad() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	batching_add_quad_internal(batch, texture, verts, trapezoidal_correction);
}
void batching_add_tri(int texture, vertex *verts)
{
	Assertion((texture >= 0), "batching_add_tri() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	primitive_batch *batch = batching_find_batch(texture, batch_info::FLAT_EMISSIVE);

	batching_add_tri_internal(batch, texture, verts);
}

void batching_add_tri(int texture, vertex *verts, primitive_batch *batch)
{
	Assertion((texture >= 0), "batching_add_tri() attempted for invalid texture");
	if ( texture < 0 ) {
		return;
	}

	batching_add_tri_internal(batch, texture, verts);
}

void batching_render_batch_item(primitive_batch_item* item,
	vertex_layout* layout,
	primitive_type prim_type,
	gr_buffer_handle buffer_num)
{
	GR_DEBUG_SCOPE("Batching render item");
	TRACE_SCOPE(tracing::RenderBatchItem);

	if ( item->batch_item_info.mat_type == batch_info::VOLUME_EMISSIVE ) { // Cmdline_softparticles
		particle_material material_def;

		material_set_unlit_volume(&material_def, item->batch_item_info.texture, prim_type == PRIM_TYPE_POINTS);
		gr_render_primitives_particle(&material_def, prim_type, layout, (int)item->offset, (int)item->n_verts, buffer_num);
	} else if ( item->batch_item_info.mat_type == batch_info::DISTORTION ) {
		distortion_material material_def;

		material_set_distortion(&material_def, item->batch_item_info.texture, item->batch_item_info.thruster);
		gr_render_primitives_distortion(&material_def, PRIM_TYPE_TRIS, layout, (int)item->offset, (int)item->n_verts, buffer_num);
	} else if ( item->batch_item_info.mat_type == batch_info::FLAT_OPAQUE) {
		batched_bitmap_material material_def;

		material_set_batched_opaque_bitmap(&material_def, item->batch_item_info.texture, 2.0f);
		gr_render_primitives_batched(&material_def, PRIM_TYPE_TRIS, layout, (int)item->offset, (int)item->n_verts, buffer_num);
	} else {
		batched_bitmap_material material_def;

		material_set_batched_bitmap(&material_def, item->batch_item_info.texture, 1.0f, 2.0f);
		gr_render_primitives_batched(&material_def, PRIM_TYPE_TRIS, layout, (int)item->offset, (int)item->n_verts, buffer_num);
	}
}

void batching_allocate_and_load_buffer(primitive_batch_buffer *draw_queue)
{
	Assert(draw_queue != NULL);

	if ( draw_queue->buffer_size < draw_queue->desired_buffer_size ) {
		if ( draw_queue->buffer_ptr != NULL ) {
			vm_free(draw_queue->buffer_ptr);
		}

		draw_queue->buffer_size = draw_queue->desired_buffer_size;
		draw_queue->buffer_ptr = vm_malloc(draw_queue->desired_buffer_size);
	}

	draw_queue->desired_buffer_size = 0;
	
	size_t offset = 0;
	size_t num_items = draw_queue->items.size();

	for ( size_t i = 0; i < num_items; ++i ) {
		primitive_batch_item *item = &draw_queue->items[i];

		item->offset = offset;
		item->n_verts = item->batch->load_buffer((batch_vertex*)draw_queue->buffer_ptr, offset);
		item->batch->clear();
		
		offset += item->n_verts;
	}

	// if there are no items in this batch, we will never render it and thus there is no need to update it in vmem
	if (num_items && draw_queue->buffer_num.isValid()) {
		gr_update_buffer_data(draw_queue->buffer_num, draw_queue->buffer_size, draw_queue->buffer_ptr);
	}
}

void batching_load_buffers(bool distortion)
{
	GR_DEBUG_SCOPE("Batching load buffers");
	TRACE_SCOPE(tracing::LoadBatchingBuffers);

	for (auto &buffer_iter : Batching_buffers) {
		// zero out the buffers
		buffer_iter.second.desired_buffer_size = 0;
	}

	// assign primitive batch items
	for (auto &bi : Batching_primitives) {
		if ( bi.first.mat_type == batch_info::DISTORTION ) {
			if ( !distortion ) {
				continue;
			}
		} else {
			if ( distortion ) {
				continue;
			}
		}

		size_t num_verts = bi.second.num_verts();

		if ( num_verts > 0 ) {
			batch_info render_info = bi.second.get_render_info();
			uint vertex_mask = batching_determine_vertex_layout(&render_info);

			primitive_batch_buffer *buffer = batching_find_buffer(vertex_mask, render_info.prim_type);
			primitive_batch_item draw_item;

			draw_item.batch_item_info = render_info;
			draw_item.offset = 0;
			draw_item.n_verts = num_verts;
			draw_item.batch = &bi.second;

			buffer->desired_buffer_size += num_verts * sizeof(batch_vertex);
			buffer->items.push_back(draw_item);
		}
	}

	for (auto &buffer_iter : Batching_buffers) {
		batching_allocate_and_load_buffer(&buffer_iter.second);
	}
}

void batching_render_buffer(primitive_batch_buffer *buffer)
{
	GR_DEBUG_SCOPE("Batching render buffer");
	TRACE_SCOPE(tracing::RenderBatchBuffer);

	size_t num_batches = buffer->items.size();

	for ( int j = 0; j < batch_info::NUM_RENDER_TYPES; ++j ) {
		for ( size_t i = 0; i < num_batches; ++i ) {
			if ( buffer->items[i].batch_item_info.mat_type != j ) {
				continue;
			}

			batching_render_batch_item(&buffer->items[i], &buffer->layout, buffer->prim_type, buffer->buffer_num);
		}
	}

	buffer->items.clear();
}

void batching_render_all(bool render_distortions)
{
	GR_DEBUG_SCOPE("Batching render all");
	TRACE_SCOPE(tracing::DrawEffects);

	batching_load_buffers(render_distortions);

	SCP_map<batch_buffer_key, primitive_batch_buffer>::iterator bi;

	for ( bi = Batching_buffers.begin(); bi != Batching_buffers.end(); ++bi ) {
		batching_render_buffer(&bi->second);
	}
	
	gr_clear_states();
}

void batching_shutdown()
{
	for ( auto buffer_iter = Batching_buffers.begin(); buffer_iter != Batching_buffers.end(); ++buffer_iter ) {
		primitive_batch_buffer *batch_buffer = &buffer_iter->second;

		if ( batch_buffer->buffer_ptr != NULL ) {
			vm_free(batch_buffer->buffer_ptr);
			batch_buffer->buffer_ptr = nullptr;
		}
	}
}
