
#ifndef __tmeshview_h__
#include "TMeshView.h"
#endif

#ifndef __tmeshform_h__
#include "TMeshForm.h"
#endif

#ifndef PandaWave_XMESSAGES
#include <XMessages.h>
#endif

#ifndef __texturestack_h__
#include "texturestack.h"
#endif

#ifndef __skelmodel_h__
#include "skelmodel.h"
#endif

#ifndef __memalign_h__
#include "memalign.h"
#endif

#ifndef __read_skl_h__
#include "read_skl.h"
#endif

#ifndef __scen_h__
#include "scen.h"
#endif

#ifndef __mons_h__
#include "mons.h"
#endif

#ifndef __obje_h__
#include "obje.h"
#endif

#ifndef __proj_h__
#include "proj.h"
#endif

#ifndef __unit_h__
#include "unit.h"
#endif

#include <ctype.h>

#include <XDialog.h>

#ifndef PandaWave_RESOURCES
#include "RevengeResources.h"
#endif

#ifndef PandaWave_XTEXTCONTROLS
#include <XTextControls.h>
#endif

#include "formatters.h"

#include <XError.h>

#ifdef _WIN32
#include <gl/glext.h>
#endif

#include <glu.h>
#include "jpeg.h"

#define AXIS_LENGTH (0.4f)


#if 1 || defined(macintosh)
#define assert(A) ((void)0)
#else
#define enter_debugger() __asm {int 3}
#define assert(expr) if (!(expr)) { enter_debugger(); /* exit(-1); */ }
#endif

#define TERRAIN_OPACITY 0x80

unsigned char TMeshView::terrain_type_colors[16][4] =
{
	{ 0x00, 0x00, 0xFF, TERRAIN_OPACITY }, //_terrain_media_dwarf_depth
	{ 0x00, 0x00, 0xB0, TERRAIN_OPACITY }, //_terrain_media_human_depth
	{ 0x00, 0x00, 0x80, TERRAIN_OPACITY }, //_terrain_media_giant_depth
	{ 0x00, 0x00, 0x40, TERRAIN_OPACITY }, //_terrain_media_deep
	{ 0x80, 0x00, 0x00, TERRAIN_OPACITY }, //_terrain_sloped
	{ 0xFF, 0x00, 0x00, TERRAIN_OPACITY }, //_terrain_steep
	{ 0x00, 0x00, 0x00, TERRAIN_OPACITY }, //_terrain_grass
	{ 0xFF, 0xFF, 0x00, TERRAIN_OPACITY }, //_terrain_desert
	{ 0x20, 0x20, 0x20, TERRAIN_OPACITY }, //_terrain_rocky
	{ 0x80, 0x40, 0x00, TERRAIN_OPACITY }, //_terrain_marsh
	{ 0xA0, 0xA0, 0xA0, TERRAIN_OPACITY }, //_terrain_snow
	{ 0x20, 0x70, 0x20, TERRAIN_OPACITY }, //_terrain_forest
	{ 0xFF, 0x00, 0xFF, TERRAIN_OPACITY }, //_terrain_loathing_special
	{ 0x00, 0x00, 0x00, TERRAIN_OPACITY }, //_terrain_unused
	{ 0x00, 0x80, 0x00, TERRAIN_OPACITY }, //_terrain_walking_impassable
	{ 0x00, 0xFF, 0x00, TERRAIN_OPACITY }  //_terrain_flying_impassable
};


inline short_world_distance& mutable_cell_world_height( struct mesh_cell *cell, int which_height )
{
	assert( which_height >= 0 && which_height < MAXIMUM_CELL_HEIGHTS );
	return ((short_world_distance *)cell)[which_height];
}


inline short_world_distance cell_world_height( const struct mesh_cell *cell, int which_height )
{
	if (which_height == _cell_render_physical_max)
	{
		return MAX(cell->render_height,cell->physical_height);
	}
	assert( which_height >= 0 && which_height < MAXIMUM_CELL_HEIGHTS );
	return ((short_world_distance *)cell)[which_height];
}

inline float cell_float_height( const struct mesh_cell *cell, int which_height )
{
	return (float)cell_world_height( cell, which_height ) * (1.0f / 512.0f);
}

int TMeshView::gl_has_extension( const char* str)
{
	int idx1, idx2, i, k;
	int ext_string_len;
	int test_string_len;
	const unsigned char* gl_ext_string = glGetString(GL_EXTENSIONS);

	if(gl_ext_string == 0 || str == 0)
		return 0;

	test_string_len = strlen(str);
	ext_string_len = strlen((const char *)gl_ext_string);
	idx1 = idx2 = 0;
	for(i = 0; i < ext_string_len; i++)
	{
		if(gl_ext_string[i] != ' ')
		{
			idx1=i;
			for(idx2=idx1+1; idx2 < ext_string_len; idx2++)
			{
				if(gl_ext_string[idx2] == ' ')
				{
					idx2--;
					break;
				}
			}
			if(idx2 == ext_string_len)
				idx2--;

			// can it match?
			if((idx2-idx1)+1 == test_string_len)
			{
				// i don't think the mac has strnicmp so i just did it myself here.
				for(k = 0; k < test_string_len; k++)
				{
					if(tolower(str[k]) != tolower(gl_ext_string[k+idx1]))
						break;
				}

				if(k == test_string_len)	// we have a winner.
					return 1;
			}
		}
	}

	return 0;
}

void TMeshView::gl_setup_imp_extensions( void )
{
	gl_has_multitexture = gl_has_extension("GL_ARB_multitexture");
	if(gl_has_multitexture)
	{

	#ifdef _WIN32

		// load up ARB calls on win32.
		q_glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
		q_glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
		q_glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTextureCoord2fARB");

	#elif defined(macintosh)

		q_glActiveTextureARB = glActiveTextureARB;
		q_glClientActiveTextureARB = glClientActiveTextureARB;
		q_glMultiTexCoord2fARB = glMultiTexCoord2fARB;

	#endif
	}
	else
	{
		q_glActiveTextureARB = 0;
		q_glClientActiveTextureARB = 0;
		q_glMultiTexCoord2fARB = 0;
	}
	
	#ifdef _WIN32
	
	q_glDrawRangeElementsEXT = (PFNGLDRAWRANGEELEMENTSEXTPROC)wglGetProcAddress("glDrawRangeElementsEXT");
	q_glLockArraysEXT = (PFNGLLOCKARRAYSEXTPROC)wglGetProcAddress("glLockArraysEXT");
	q_glUnlockArraysEXT = (PFNGLUNLOCKARRAYSEXTPROC)wglGetProcAddress("glUnlockArraysEXT");
	
	#elif defined(macintosh)
	
	q_glDrawRangeElementsEXT = NULL;
	q_glLockArraysEXT = glLockArraysEXT;
	q_glUnlockArraysEXT = glUnlockArraysEXT;
	
	#endif
}


#define MAX_MARKERS 8192
static float marker_vectors[MAX_MARKERS*(6+28)][3];
static byte marker_colors[MAX_MARKERS*(6+28)][4];
static byte marker_axis_colors[5][6][4];
static byte marker_box_colors[28][4];
static byte marker_box_selected_colors[28][4];

inline void MatMult( float out[3][3], const float a[3][3], const float b[3][3] )
{
	float temp[3][3];
	for (int i = 0; i < 3; ++i) {
		for (int j = 0; j < 3; ++j) {
			temp[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
		}
	}
	memcpy( out, temp, sizeof(temp) );
}


void make_rotation_matrix_radians( float out[3][3], float pitch, float roll, float yaw )
{
	yaw += 3.14159265358979 * 0.5;
	float sp = sin(pitch);
	float cp = cos(pitch);
	float sr = sin(roll);
	float cr = cos(roll);
	float sy = sin(yaw);
	float cy = cos(yaw);
	float m_yaw[3][3] = {	{   cy,   sy, 0.0f },
							{  -sy,   cy, 0.0f },
							{ 0.0f, 0.0f, 1.0f } };
	
	float m_roll[3][3] = {	{   cr, 0.0f,   sr },
							{ 0.0f, 1.0f, 0.0f },
							{  -sr, 0.0f,   cr } };
	
	float m_pitch[3][3] = {	{ 1.0f, 0.0f, 0.0f },
							{ 0.0f,   cp,  -sp },
							{ 0.0f,   sp,   cp } };
	
	float m_temp[3][3];
	MatMult( m_temp, m_roll, m_pitch );
	MatMult( out, m_temp, m_yaw );
}

inline void make_rotation_matrix_mythangle( float out[3][3], angle pitch, angle roll, angle yaw )
{
	double scale = 3.14159265358979/HALF_CIRCLE;
	
	make_rotation_matrix_radians( out, pitch * scale, roll * scale, yaw * scale );
#if 0
	for (int i = 0; i < 3; ++i) {
		for (int j = 0; j < 3; ++j) {
			out[i][j] = (i == j ? 1.0f : 0.0f);
		}
	}
#endif
}


//BEGINDISPATCH(TMeshView,XGView)
//	DISPATCHTABLE(KXGKeyClick,TMeshView::DoKeyEvent)
//ENDDISPATCH

static float landscape_uv[17][17][2];
static unsigned short tri_array[16][16][6];


float distance_along_line( vec3 originA, vec3 directionA, vec3 originB, vec3 directionB )
// returns a float t such that originA + t * directionA is the closest point to the second line
{
	vec3 perp = cross( directionA, directionB );
	vec3 planeB = cross( perp, directionB );
	float planeBconstant = -dot( planeB, originB );
	float num = dot(planeB,originA) + planeBconstant;
	float denom = dot(planeB,directionA);
	return -num/denom;
}


float line_intersects_plane( vec3 origin, vec3 direction, vec3 plane_normal, float plane_constant )
{
	float denom = dot(direction,plane_normal);
	if (fabs(denom) < 0.00001f) return -1.0f;
	float num = dot(plane_normal,origin) + plane_constant;
	float dist = -num/denom;
	return dist;
}


static char *TextFloat( double value, int precision = 3 )
{
	static char buffer[256];
	
	sprintf( buffer, "%.*f", precision, value );
	char *s = buffer + strlen(buffer) - 1;
	while (s > buffer && *s == '0' && s[-1] != '.') {
		*s = 0;
		--s;
	}
	return buffer;
}

static double GetFloat( XGDialog *d, int item )
{
	char buf[256];
	d->FindViewByID( item )->GetTitle( buf );
	double value = atof( buf );
	return value;
}

struct marker_info_dlog_data {
	marker_data *m;
	TMeshForm *form;
	TMeshView *view;
};

long TMeshView::MarkerInfoProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	marker_info_dlog_data *info = (marker_info_dlog_data *)data;
	marker_data *m = info->m;
	if (!m) return -1;
	double mesh_height = info->view->get_mesh_height( GetFloat( dlog, 1 ), GetFloat( dlog, 2 ), _cell_physical_height );
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						dlog->SetText( 1, TextFloat( world_distance_to_float(m->position.x) ) );
						dlog->SetText( 2, TextFloat( world_distance_to_float(m->position.y) ) );
						dlog->SetText( 3, TextFloat( world_distance_to_float(m->position.z) ) );
						dlog->SetText( 4, TextFloat( world_distance_to_float(m->height) ) );
						dlog->SetText( 11, TextFloat( angle_to_float(m->yaw) ) );
						dlog->SetText( 12, TextFloat( angle_to_float(m->pitch) ) );
						dlog->SetText( 13, TextFloat( angle_to_float(m->roll) ) );
						for (int i = 0; i < 5; ++i)
						{
							SetFlag( dlog, 50 + i, (m->flags & (1 << i)) );
						}
						for (int i = 0; i < 3; ++i)
						{
							SetFlag( dlog, 60 + i, ((m->minimum_difficulty_level >> 1) == i) );
						}
					} return 0;
			} break;
		case KXGKeyClick:
			switch (arg) {
				case 1:
				case 2:
					if (dlog->GetValue( 5 )) {
						dlog->SetText( 4, TextFloat( GetFloat(dlog,3) - mesh_height ) );
					} else if (dlog->GetValue( 6 )) {
						dlog->SetText( 3, TextFloat( GetFloat(dlog,4) + mesh_height ) );
					}
					return 0;
				case 3:
					dlog->SetText( 4, TextFloat( GetFloat(dlog,3) - mesh_height ) );
					return 0;
				case 4:
					dlog->SetText( 3, TextFloat( GetFloat(dlog,4) + mesh_height ) );
					return 0;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 5:
				case 6:
					for (int i = 5; i <= 6; ++i) {
						dlog->SetValue( i, i == arg );
					}
					return 0;
				case 50:
				case 51:
				case 52:
				case 53:
				case 54:
					SetFlag( dlog, arg, !GetFlag( dlog, arg ) );
					return 0;
				case 60:
				case 61:
				case 62:
					for (int i = 0; i < 3; ++i)
					{
						SetFlag( dlog, 60 + i, ((60 + i) == arg) );
					}
					return 0;
				case 100:
					m->position.x = float_to_world_distance(GetFloat( dlog, 1 ));
					m->position.y = float_to_world_distance(GetFloat( dlog, 2 ));
					m->position.z = float_to_world_distance(GetFloat( dlog, 3 ));
					m->height = float_to_world_distance(GetFloat( dlog, 4 ));
					m->yaw = float_to_angle(GetFloat( dlog, 11 ));
					m->pitch = float_to_angle(GetFloat( dlog, 12 ));
					m->roll = float_to_angle(GetFloat( dlog, 13 ));
					m->flags = 0;
					for (int i = 0; i < 5; ++i)
					{
						if (GetFlag( dlog, 50 + i ))
						{
							m->flags |= (1 << i);
						}
					}
					for (int i = 0; i < 3; ++i)
					{
						if (GetFlag( dlog, 60 + i ))
						{
							m->minimum_difficulty_level = i * 2;
						}
					}
					dlog->EndDialog( 1 );
					return 0;
				case 101:
					dlog->EndDialog( 0 );
					return 0;
			} break;
	}
	
	return -1;
}


TMeshView::TMeshView( XGView *parent, XGArgStream &s ):
	XGOpenGLView( parent, s )
{
	subsection_width = subsection_height = 0;
	mesh_width = mesh_height = 0;
	form = 0;
	drag_mode = drag_mode_none;
	targetHeightKind = _cell_render_height;
	ClearMeshHeightChangeMask();
	meshHeightMask[_cell_physical_height] = true;
	meshHeightMask[_cell_render_height] = true;
	moveMarkersWithMesh = true;
	
	int x, y, i, j;
	
	for (y = 0; y < 17; ++y)
	{
		for (x = 0; x < 17; ++x)
		{
			landscape_uv[y][x][0] = (127.0 - (126.0 / 16.0) * x) / 128.0;
			landscape_uv[y][x][1] = (1.0 + (126.0 / 16.0) * y) / 128.0;
		}
	}
	
	for (y = 0; y < 16; ++y)
	{
		for (x = 0; x < 16; ++x)
		{
			int v = y*17+x;
			if (!((x^y)&1))
			{
				tri_array[y][x][0] = v;
				tri_array[y][x][1] = v+17;
				tri_array[y][x][2] = v+17+1;
				
				tri_array[y][x][3] = v;
				tri_array[y][x][4] = v+17+1;
				tri_array[y][x][5] = v+1;
			}
			else
			{
				tri_array[y][x][0] = v;
				tri_array[y][x][1] = v+17;
				tri_array[y][x][2] = v+1;
				
				tri_array[y][x][3] = v+1;
				tri_array[y][x][4] = v+17;
				tri_array[y][x][5] = v+17+1;
			}
		}
	}
	
	for (i = 0; i < 5; ++i)
	{
		for (j = 0; j < 6; ++j)
		{
			marker_axis_colors[i][j][0] = 0;
			marker_axis_colors[i][j][1] = 0;
			marker_axis_colors[i][j][2] = 0;
			marker_axis_colors[i][j][3] = 0x40;
			marker_axis_colors[i][j][j>>1] = 0xff;
		}
	}
	
	for (j = 0; j < 3; ++j)
	{
		marker_axis_colors[1][j<<1][0] = 0xff;
		marker_axis_colors[1][j<<1][1] = 0xff;
		marker_axis_colors[1][j<<1][2] = 0;
		
		marker_axis_colors[2+j][(j<<1)][0] = 0xff;
		marker_axis_colors[2+j][(j<<1)][1] = 0xff;
		marker_axis_colors[2+j][(j<<1)][2] = 0;
		
		marker_axis_colors[2+j][(j<<1)+1][0] = 0xff;
		marker_axis_colors[2+j][(j<<1)+1][1] = 0xff;
		marker_axis_colors[2+j][(j<<1)+1][2] = 0;
	}
	
	for (i = 0; i < 28; ++i)
	{
		marker_box_colors[i][0] = 0x00;
		marker_box_colors[i][1] = 0xb0;
		marker_box_colors[i][2] = 0xb0;
		marker_box_colors[i][3] = 0x40;
		
		marker_box_selected_colors[i][0] = 0xff;
		marker_box_selected_colors[i][1] = 0xff;
		marker_box_selected_colors[i][2] = 0x00;
		marker_box_selected_colors[i][3] = 0x40;
	}
	
	bound_texture_number = -1;
	extensions_done = false;
	
	markerWorldAxis = false;
	showMeshPolys = true;
	showTerrainType = false;
	for (i = 0; i < MAXIMUM_CELL_HEIGHTS; ++i)
	{
		meshHeightShown[i] = true;
	}
	markerViewMode = KMenuMeshSimpleModels;
	
	dragged_marker = 0;
	dragged_cell = 0;
	
	softBrushRadius = 2.5f;
	softBrushRoundness = 1.0f;
	softBrushSoftness = 1.0f;
	
	targetMediaHeight = 0;
}


TMeshView::~TMeshView()
{
	ResetAllTextures();
	ResetAllSkelmodels();
}


void TMeshView::ResetAllTextures()
{
	while (txst_caches.Length())
	{
		ResetTexture( txst_cache_info[txst_caches.Length()-1].id );
	}
	txst_cache_info.DeleteAll();
	txst_caches.DeleteAll();
}


void TMeshView::ResetTexture( tag id )
{
	for (int i = 0; i < txst_cache_info.Length(); ++i)
	{
		if (txst_cache_info[i].id == id)
		{
			for (int j = 0; j < txst_caches[i].Length(); ++j)
			{
				for (int k = 0; k < txst_caches[i][j].Length(); ++k)
				{
					glDeleteTextures( 1, (GLuint *) &(txst_caches[i][j][k].gl_texture_number) );
				}
			}
			txst_cache_info.Delete( i );
			txst_caches.Delete( i );
			return;
		}
	}
}


void TMeshView::ResetAllSkelmodels()
{
	for (int i = 0; i < skelcache.Length(); ++i)
	{
		free( skelcache[i].vertices );
		free( skelcache[i].normals );
		free( skelcache[i].uv );
		free( skelcache[i].indices );
	}
	skelcache.DeleteAll();
}


void TMeshView::ResetSkelmodel( tag id )
{
	for (int i = 0; i < skelcache.Length(); ++i)
	{
		if (skelcache[i].id == id)
		{
			free( skelcache[i].vertices );
			free( skelcache[i].normals );
			free( skelcache[i].uv );
			free( skelcache[i].indices );
			skelcache.Delete(i);
			--i;
		}
	}
}


TMeshView::cached_skelmodel_data *TMeshView::get_skelmodel( tag id, int permutation )
{
	int i, j;
	
	for (i = 0; i < skelcache.Length(); ++i)
	{
		if (skelcache[i].id == id && skelcache[i].permutation == permutation)
		{
			return &(skelcache[i]);
		}
	}
	
	tag_entry *entry = get_entry( 'skmd', id );
	if (!entry) return 0;
	const void *data = get_entry_data( entry );
	if (!data) return 0;
	
	skmd_tag_header taghead;
	byte_swap_move_be( "skmd_header", &taghead, (void*)data, sizeof(taghead), 1, bs_skmd_tag_header );
	if (taghead.model_count == 0) return 0;
	if (permutation < 0 || permutation >= taghead.model_count) permutation = 0;
	skmd_modelheader *buf = (skmd_modelheader *)alloc_align( taghead.models[permutation].length, 16 );
	memcpy( buf, (byte *)data + taghead.models[permutation].offset, taghead.models[permutation].length );
	unsigned hlen = import_skmd_modelheader( buf );
	int v_count = (markerViewMode == KMenuMeshComplexModels ? buf->max_vertices : buf->user_min_vertices);
	
	v_count = buf->real_vertex_counts[v_count];
	
	int best_lod = 0;
	for (i = 1; i < buf->lod_count; ++i)
	{
		if (buf->lods[i].min_vertices <= v_count && buf->lods[i].max_vertices >= v_count)
		{
			best_lod = i;
			break;
		}
	}
	
	if (v_count < buf->lods[best_lod].min_vertices)
	{
		v_count = buf->lods[best_lod].min_vertices;
	}
	else if (v_count > buf->lods[best_lod].max_vertices)
	{
		v_count = buf->lods[best_lod].max_vertices;
	}
	
	skmd_lod *lod = (skmd_lod *)(buf->lods[best_lod].data);
	int lodlen;
	lodlen = import_skmd_lod( buf->bone_count, buf->lods[best_lod].min_vertices, buf->lods[best_lod].max_vertices, lod );
	assert( lodlen == buf->lods[best_lod].length );
	
	cached_skelmodel_data newdata;
	newdata.id = id;
	newdata.texture = buf->texture_stack;
	newdata.permutation = permutation;
	newdata.vertex_count = v_count;
	newdata.vertices = (float *)malloc( 4 * sizeof(float) * v_count );
	newdata.normals = (float *)malloc( 4 * sizeof(float) * v_count );
	newdata.uv = (float *)malloc( 2 * sizeof(float) * v_count );
	unsigned short tri_indices[4000*3];
	int tri_count;
	
	object_bone *bone = new object_bone[buf->bone_count + 1];
	
	for (i = 0; i < 3; ++i) {
		bone->world_pos[i] = 0.0f;
		for (j = 0; j <3; ++j) {
			bone->m[i][j] = (i == j ? buf->model_scale : 0.0f);
		}
	}
	
	++bone;
	
	for (i = 0; i < buf->bone_count; ++i) {
		int parent = buf->bones[i].parent;
		QuatToMat( bone[i].m, buf->bones[i].float_orient );
		MatMult( bone[i].m, bone[parent].m, bone[i].m );
		//QuatMult( bone[i].q, mh->bones[i].orient, bone[mh->bones[i].parent].q );
		//QuatToMat( bone[i].m, bone[i].q );
		for (j = 0; j < 3; ++j) {
			const float (*m)[3] = bone[parent].m;
			bone[i].world_pos[j] =  m[j][0] * buf->bones[i].float_offset[0] +
									m[j][1] * buf->bones[i].float_offset[1] +
									m[j][2] * buf->bones[i].float_offset[2] +
									bone[parent].world_pos[j];
		}
	}
	
	generate_skel_mesh( bone, buf->bone_count, &(buf->lods[best_lod]),
						v_count, newdata.vertices, newdata.normals,
						tri_indices, &tri_count );
	
	for (i = 0; i < 3; ++i) {
		for (j = 0; j < 2; ++j) {
			newdata.bounds[i][j] = 0.0f;
		}
	}
	
	for (i = 0; i < v_count; ++i)
	{
		for (j = 0; j < 3; ++j)
		{
			float f = newdata.vertices[(i << 2)+j];
			if (f < newdata.bounds[j][0]) newdata.bounds[j][0] = f;
			if (f > newdata.bounds[j][1]) newdata.bounds[j][1] = f;
		}
	}
	
	newdata.index_count = tri_count * 3;
	newdata.indices = (unsigned short *)malloc( 3 * sizeof(unsigned short) * tri_count );
	memcpy( newdata.indices, tri_indices, 3 * sizeof(unsigned short) * tri_count );
	
	memcpy( newdata.uv, buf->uv, 2 * sizeof(float) * v_count );
	
	skelcache += newdata;
	
	--bone;
	delete [] bone;
	free_align( buf );
	
	return &(skelcache[skelcache.Length()-1]);
}


int TMeshView::get_txst( tag id )
{
	for (int i = 0; i < txst_cache_info.Length(); ++i)
	{
		if (txst_cache_info[i].id == id)
		{
			return i;
		}
	}
	
	tag_entry *txst = get_entry( 'txst', id );
	if (!txst) return -1;
	const byte *raw = (const byte *)get_entry_data( txst );
	if (!raw) return -1;
	
	int index = txst_cache_info.Length();
	
	cached_txst_info info;
	info.id = id;
	info.entry = txst;
	txst_cache_info += info;
	
	XGDynArray<stack_cache> empty;
	txst_caches += empty;
	
	txst_tagheader head;
	byte_swap_move_be( "", &head, raw, sizeof(head), 1, swap_txst_tagheader );
	txst_caches[index].SetLength( head.count );
	
	return index;
}


int TMeshView::BindTexture( tag id, int stacknumber, int index, int layer, int use )
{
	int i;
	
	int cache_index = get_txst( id );
	if (cache_index < 0) return -1;
	if (stacknumber < 0 || stacknumber >= txst_caches[cache_index].Length()) return -1;
	stack_cache &stack = txst_caches[cache_index][stacknumber];
	
	for (i = 0; i < stack.Length(); ++i)
	{
		if (stack[i].index == index || ((layer < 0 || stack[i].layer == layer) && stack[i].use == use))
		{
			if (bound_texture_number != stack[i].gl_texture_number)
			{
				bound_texture_number = stack[i].gl_texture_number;
				glBindTexture( GL_TEXTURE_2D, bound_texture_number );
			}
			return bound_texture_number;
		}
	}
	
	tag_entry *txst = txst_cache_info[cache_index].entry;
	const byte *raw = (const byte *)get_entry_data( txst );
	if (!txst) return 0;
	txst_tagheader head;
	byte_swap_move_be( "", &head, raw, sizeof(head), 1, swap_txst_tagheader );
	if (stacknumber >= head.count) return -1;
	txst_stackref stackref;
	byte_swap_move_be( "stackref", &stackref, raw+sizeof(head)+stacknumber*sizeof(stackref), sizeof(stackref), 1, swap_txst_stackref );
	txst_stackheader stackhead;
	byte_swap_move_be( "stackhead", &stackhead, raw+stackref.offset, sizeof(stackhead), 1, swap_txst_stackheader );
	txst_texheader texhead;
	if (index < 0 || index >= stackhead.count)
	{
		index = 0;
		for (;;)
		{
			byte_swap_move_be( "texhead", &texhead, raw+stackref.offset+sizeof(stackhead)+index*sizeof(texhead), sizeof(texhead), 1, swap_txst_texheader );
			if ((layer < 0 || layer == texhead.layer) && use == texhead.use)
			{
				break;
			}
			if (++index >= stackhead.count)
			{
				return -1;
			}
		}
	}
	byte_swap_move_be( "texhead", &texhead, raw+stackref.offset+sizeof(stackhead)+index*sizeof(texhead), sizeof(texhead), 1, swap_txst_texheader );
	cached_texture_data new_info;
	new_info.index = index;
	new_info.use = texhead.use;
	new_info.layer = texhead.layer;
	glGenTextures( 1, (GLuint *)&new_info.gl_texture_number );
	stack += new_info;
	bound_texture_number = new_info.gl_texture_number;
	glBindTexture( GL_TEXTURE_2D, new_info.gl_texture_number );
	
	const void *image = raw+stackref.offset+texhead.offset;
	byte *jpeg_buf = 0;
	if (texhead.format == 1)
	{
		jpeg_buf = (byte *)malloc( texhead.width * texhead.height * texhead.channels );
		decompress_jpeg( jpeg_buf, image, texhead.length, texhead.width, texhead.height, texhead.channels, false );
		image = jpeg_buf;
	}
	static const int internal_formats[] = { 0, GL_LUMINANCE4, GL_LUMINANCE4_ALPHA4, GL_RGB5, GL_RGBA4 };
	static const int pixel_types[] = { 0, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA };
	gluBuild2DMipmaps( GL_TEXTURE_2D, internal_formats[texhead.channels],
						texhead.width, texhead.height, pixel_types[texhead.channels],
						GL_UNSIGNED_BYTE, image );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	if (jpeg_buf)
	{
		free( jpeg_buf );
		jpeg_buf = 0;
	}
	return new_info.gl_texture_number;
}


void TMeshView::ResetMarkers()
{
	marker_cache.DeleteAll();
	if (!form) return;
	marker_cache.SetLength( form->markers.Length() );
	for (int i = 0; i < form->markers.Length(); ++i)
	{
		MarkerChanged( i );
	}
}


void TMeshView::MarkerChanged( int index )
{
	cached_marker_data &c = marker_cache[index];
	marker_data &m = form->markers[index];
	make_rotation_matrix_mythangle( c.mat, m.pitch, m.roll, m.yaw );
	float mpx = m.position.x / 512.0;
	float mpy = m.position.y / 512.0;
	float mpz = m.position.z / 512.0;
	if (!markerWorldAxis)
	{
		for (int i = 0; i < 3; ++i)
		{
			c.endpoints[i][0][0] = mpx;
			c.endpoints[i][0][1] = mpy;
			c.endpoints[i][0][2] = mpz;
			c.endpoints[i][1][0] = mpx + AXIS_LENGTH * c.mat[i][0];
			c.endpoints[i][1][1] = mpy + AXIS_LENGTH * c.mat[i][1];
			c.endpoints[i][1][2] = mpz + AXIS_LENGTH * c.mat[i][2];
		}
	}
	else
	{
		for (int i = 0; i < 3; ++i)
		{
			c.endpoints[i][0][0] = mpx;
			c.endpoints[i][0][1] = mpy;
			c.endpoints[i][0][2] = mpz;
			c.endpoints[i][1][0] = mpx;
			c.endpoints[i][1][1] = mpy;
			c.endpoints[i][1][2] = mpz;
			c.endpoints[i][1][i] += AXIS_LENGTH;
		}
	}
}


void TMeshView::ResetMesh( TMeshForm *form )
{
	this->form = form;
	ResetAllTextures();
	ResetAllSkelmodels();
	ResetMarkers();
	ClearSelection();
	if (form) {
		subsection_width = form->header.submesh_width * 2;
		subsection_height = form->header.submesh_height * 2;
		mesh_width = form->header.submesh_width * 32;
		mesh_height = form->header.submesh_height * 32;
	}
	
	dragged_cell = 0;
	dragged_marker = 0;
	
	InvalView();
}


void TMeshView::ClearSelection()
{
	cellSelect.Empty();
	markerSelect.Empty();
	drag_mode = drag_mode_none;
	dragged_marker = 0;
	dragged_cell = 0;
	marker_drag_start.DeleteAll();
}


void TMeshView::UpdateStatusBar()
{
	XGStaticText *stat = dynamic_cast<XGStaticText *>(GetParent()->FindViewByID(2));
	if (!stat) return;
	char buf[256];
	
	buf[0] = 0;
	
	if (dragged_cell && targetHeightKind > 0)
	{
		sprintf( buf, "x: %.3f    y: %.3f    z: %.3f",
						(double)((dragged_cell - form->cells) % form->width),
						(double)((dragged_cell - form->cells) / form->width),
						cell_float_height( dragged_cell, targetHeightKind ) );
	}
	else if (dragged_marker)
	{
		sprintf( buf, "x: %.3f    y: %.3f    z: %.3f    elev: %.3f       yaw: %.1f    pitch: %.1f    roll %.1f",
						dragged_marker->position.x / 512.0,
						dragged_marker->position.y / 512.0,
						dragged_marker->position.z / 512.0,
						dragged_marker->height / 512.0,
						dragged_marker->yaw * 360.0 / FULL_CIRCLE,
						dragged_marker->pitch * 360.0 / FULL_CIRCLE,
						dragged_marker->roll * 360.0 / FULL_CIRCLE );
	}
	stat->SetTitle( buf );
}


inline int TMeshView::get_terrain_vert_index( int base_x, int base_y, int vx, int vy, int terrain_type, vec3 *mesh_verts, unsigned *mesh_colors, word mesh_mask[17][17], short mesh_index[16][17][17], int *vcount )
{
	if (mesh_mask[vy][vx] & (1 << terrain_type))
	{
		return mesh_index[terrain_type][vy][vx];
	}
	int index = *vcount;
	*vcount += 1;
	mesh_index[terrain_type][vy][vx] = index;
	mesh_mask[vy][vx] |= (1 << terrain_type);
	mesh_cell *cell = form->get_cell( base_x + vx, base_y + vy );
	mesh_colors[index] = *(unsigned *)(terrain_type_colors[terrain_type]);
	mesh_verts[index].x = base_x + vx;
	mesh_verts[index].y = base_y + vy;
	mesh_verts[index].z = 0.0f;
	if (cell)
	{
		mesh_verts[index].z = WORLD_TO_FLOAT(MAX(cell->render_height,cell->physical_height));
	}
	return index;
}


void TMeshView::DoDrawView( XRect )
{
	XGOpenGLDraw draw( this );
	if (!extensions_done)
	{
		gl_setup_imp_extensions();
		extensions_done = true;
	}
	float view_fx0, view_fy0, view_fx1, view_fy1;
	int view_sx0, view_sy0, view_sx1, view_sy1;
	int i, j, k;
	
	//glClearColor( 1.0, 1.0, 1.0, 0.0 );
	glClearColor( 0,0,0, 0.0 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	if (!form)
	{
		glFinish();
		this->ShowBackBuffer();
		return;
	}
	
	float near_dist = form->view_dist * 0.01f;
	float far_dist = form->view_dist * 3.0f;
	float max_fov = 50.0f;
	float tan_half_max_fov = tanf( 0.5f * max_fov );
	{
		XRect bounds = GetContentRect();
		int width = bounds.right - bounds.left;
		int height = bounds.bottom - bounds.top;
		float vx, vy;
		vx = vy = tan_half_max_fov * near_dist;
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		if (width < height)
		{
			vy *= (float)height / (float)width;
		}
		else
		{
			vx *= (float)width / (float)height;
		}
		glFrustum( -vx, vx, -vy, vy, near_dist, far_dist );
		view_fx0 = view_fx1 = form->view_origin.x;
		view_fy0 = view_fy1 = form->view_origin.y;
		for (i = 0; i < 2; ++i)
		{
			vec3 corner = form->view_origin + far_dist * form->view_forward;
			vec3 move = form->view_right * vx * far_dist / near_dist;
			if (i) corner += move; else corner -= move;
			for (j = 0; j < 2; ++j)
			{
				move = form->view_up * vy * far_dist / near_dist;
				if (j) corner += move; else corner -= move;
				if (corner.x < view_fx0) view_fx0 = corner.x;
				if (corner.x > view_fx1) view_fx1 = corner.x;
				if (corner.y < view_fy0) view_fy0 = corner.y;
				if (corner.y > view_fy1) view_fy1 = corner.y;
			}
		}
		view_sx0 = floor(view_fx0 / 16.0);
		view_sx1 = ceil(view_fx1 / 16.0);
		view_sy0 = floor(view_fy0 / 16.0);
		view_sy1 = ceil(view_fy1 / 16.0);
		
		if (view_sx0 < 0) view_sx0 = 0;
		if (view_sx1 > subsection_width) view_sx1 = subsection_width;
		
		if (view_sy0 < 0) view_sy0 = 0;
		if (view_sy1 > subsection_height) view_sy1 = subsection_height;
		
		if (view_sx0 >= view_sx1 || view_sy0 >= view_sy1)
		{
			glFinish();
			this->ShowBackBuffer();
			return;
		}
	}
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glRotatef( 90.0 - form->view_pitch, 1, 0, 0 );
	glRotatef( 90.0 - form->view_yaw, 0, 0, 1 );
	glTranslatef( -form->view_origin.x, -form->view_origin.y, -form->view_origin.z );
	
	glDepthFunc( GL_LEQUAL );
	glDisable( GL_BLEND );
	
	if (showMeshPolys)
	{
		vec3 mesh_verts[17][17];
		
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, sizeof(vec3), mesh_verts );
		glEnable( GL_TEXTURE_2D );
		glEnable( GL_POLYGON_OFFSET_FILL );
		glPolygonOffset( 1.0, 1.0 );
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, landscape_uv );
		glColor3f( 1.0f, 1.0f, 1.0f );
		glTexEnvi( GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_MODULATE );
		
		for (int sub_y = view_sy0; sub_y < view_sy1; ++sub_y)
		{
			for (int sub_x = view_sx0; sub_x < view_sx1; ++sub_x)
			{
				int cy = sub_y << 4;
				float fcy = cy;
				for (int vy = 0; vy < 17; ++vy)
				{
					int cx = sub_x << 4;
					float fcx = cx;
					for (int vx = 0; vx < 17; ++vx)
					{
						mesh_verts[vy][vx].x = fcx;
						mesh_verts[vy][vx].y = fcy;
						mesh_cell *cell = form->get_cell( cx, cy );
						float h = 0.0f;
						if (cell)
						{
							h = cell->render_height * (1.0f / 512.0f);
						}
						mesh_verts[vy][vx].z = h;
						++cx;
						fcx += 1.0f;
					}
					++cy;
					fcy += 1.0f;
				}
				
				if (BindTexture( form->header.landscape_texturestack_tag, (sub_y + 1) * subsection_width - (sub_x + 1), -1, -1, txst_use_basecolor ) > 0)
				{
					glDrawElements( GL_TRIANGLES, 16*16*6, GL_UNSIGNED_SHORT, tri_array );
				}
			}
		}
		
		glDisableClientState( GL_VERTEX_ARRAY );
		glDisableClientState( GL_TEXTURE_COORD_ARRAY );
		glDisable( GL_POLYGON_OFFSET_FILL );
	}
	
	if (markerViewMode != KMenuMeshMarkersOff)
	{
		for (i = 0; i < form->marker_palette.Length(); ++i)
		{
			form->marker_palette[i].render_chain = 0;
		}
		
		int marker_line_count = 0;			// Init the marker count (Might be changed)
		int max_markers = form->markers.Length();
		if (max_markers) {
			marker_data *m = &(form->markers[0]);
			cached_marker_data *c = &(marker_cache[0]);
			float *DestPtr = (float *)(marker_vectors);
			byte (*DestColors)[4] = marker_colors;
			int wx0 = view_fx0 * 512.0f;
			int wx1 = view_fx1 * 512.0f;
			int wy0 = view_fy0 * 512.0f;
			int wy1 = view_fy1 * 512.0f;
			do {
				// Do I even care?
				if ((m->position.x >= wx0) &&
					(m->position.x <= wx1) &&
					(m->position.y >= wy0) &&
					(m->position.y <= wy1))
				{
					memcpy( DestPtr, c->endpoints, sizeof(c->endpoints) );
					if (m != dragged_marker)
					{
						memcpy( DestColors, marker_axis_colors, 6*4 );
					}
					else
					{
						memcpy( DestColors, marker_axis_colors + dragged_handle, 6*4 );
					}
					DestColors += 6;
					DestPtr+=3*6;
					
					marker_palette_entry *mpe = &(form->marker_palette[m->palette_index]);
					m->render_chain = mpe->render_chain;
					mpe->render_chain = m;
				}
				++m;
				++c;
			} while (--max_markers);
			marker_line_count = (DestPtr-(float*)(marker_vectors))/3;
		}
		
		if (markerViewMode != KMenuMeshMarkersOff && markerViewMode != KMenuMeshMarkersOnly || markerSelect.Size() > 0)
		{
			glEnable( GL_TEXTURE_2D );
			glEnableClientState( GL_VERTEX_ARRAY );
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
			glEnable( GL_ALPHA_TEST );
			glAlphaFunc( GL_GREATER, 0.5f );
			glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
			glDepthFunc( GL_LESS );
			glEnable( GL_CULL_FACE );
			glCullFace( GL_FRONT );
			
			for (int marker_index = 0; marker_index < form->marker_palette.Length(); ++marker_index)
			{
				struct marker_palette_entry *mpe = &(form->marker_palette[marker_index]);
				if (!mpe->render_chain) continue;
				
				cached_skelmodel_data *skel = 0;
				object_definition obj;
				float scale = 1.0f;
				switch (mpe->type) {
					case _marker_scenery: {
							scenery_definition sd;
							if (read_scenery_definition( &sd, mpe->tag ) &&
								read_object_definition( &obj, sd.object_tag ))
							{
								skel = get_skelmodel( sd.skelmodel_tags[0], 0 );
								scale = SHORT_FIXED_TO_FLOAT( obj.scale_lower_bound ) +
										0.5f * SHORT_FIXED_TO_FLOAT( obj.scale_delta );
							}
						} break;
					case _marker_monster: {
							unit_definition ud;
							monster_definition md;
							if (read_unit_definition( &ud, mpe->tag ) &&
								read_monster_definition( &md, ud.monster_tag ) &&
								read_object_definition( &obj, md.object_tag ))
							{
								skel = get_skelmodel( md.skelmodel_tag, 0 );
								scale = SHORT_FIXED_TO_FLOAT( obj.scale_lower_bound ) +
										0.5f * SHORT_FIXED_TO_FLOAT( obj.scale_delta );
							}
						} break;
					case _marker_projectile: {
							projectile_definition pd;
							if (read_projectile_definition( &pd, mpe->tag ) &&
								read_object_definition( &obj, pd.object_tag ))
							{
								skel = get_skelmodel( pd.skelmodel_tag, 0 );
								scale = SHORT_FIXED_TO_FLOAT( obj.scale_lower_bound ) +
										0.5f * SHORT_FIXED_TO_FLOAT( obj.scale_delta );
							}
						} break;
					default:
						skel = 0;
						break;
				}
				
				if (skel)
				{
					if (markerViewMode == KMenuMeshSimpleModels || markerViewMode == KMenuMeshComplexModels)
					{
						BindTexture( skel->texture, 0, -1, -1, txst_use_basecolor );
						glVertexPointer( 3, GL_FLOAT, 16, skel->vertices );
						glTexCoordPointer( 2, GL_FLOAT, 8, skel->uv );
						if (q_glLockArraysEXT) q_glLockArraysEXT( 0, skel->vertex_count );
						for (marker_data *m = mpe->render_chain; m; m = m->render_chain)
						{
							glPushMatrix();
							glTranslatef( m->position.x * (1.0f / 512.0f), m->position.y * (1.0f / 512.0f), m->position.z * (1.0f / 512.0f) );
							glScalef( scale, scale, scale );
							glRotatef( 90.0f + m->yaw * (360.0/FULL_CIRCLE), 0.0, 0.0, 1.0 );
							glRotatef( m->pitch * (-360.0/FULL_CIRCLE), 1.0, 0.0, 0.0 );
							glRotatef( m->roll * (-360.0/FULL_CIRCLE), 0.0, 1.0, 0.0 );
							glDrawElements( GL_TRIANGLES, skel->index_count, GL_UNSIGNED_SHORT, skel->indices );
							glPopMatrix();
						}
						if (q_glUnlockArraysEXT) q_glUnlockArraysEXT();
					}
					if (markerViewMode == KMenuMeshBoundingBoxes || markerSelect.Size() > 0)
					{
						for (marker_data *m = mpe->render_chain; m; m = m->render_chain)
						{
							bool selected = markerSelect.HasMember( m );
							if (selected || markerViewMode == KMenuMeshBoundingBoxes)
							{
								cached_marker_data *c = &(marker_cache[m-&(form->markers[0])]);
								float corners[2][2][2][3];
								for (i = 0; i < 2; ++i) {
									for (j = 0; j < 2; ++j) {
										for (k = 0; k < 2; ++k) {
											corners[i][j][k][0] = c->endpoints[0][0][0] + scale * (skel->bounds[0][i] * c->mat[0][0] + skel->bounds[1][j] * c->mat[1][0] + skel->bounds[2][k] * c->mat[2][0]);
											corners[i][j][k][1] = c->endpoints[0][0][1] + scale * (skel->bounds[0][i] * c->mat[0][1] + skel->bounds[1][j] * c->mat[1][1] + skel->bounds[2][k] * c->mat[2][1]);
											corners[i][j][k][2] = c->endpoints[0][0][2] + scale * (skel->bounds[0][i] * c->mat[0][2] + skel->bounds[1][j] * c->mat[1][2] + skel->bounds[2][k] * c->mat[2][2]);
										}
									}
								}
								float (*out)[3] = marker_vectors + marker_line_count;
								for (i = 0; i < 2; ++i) {
									for (j = 0; j < 2; ++j) {
										for (k = 0; k < 3; ++k) {
											out[0][k] = corners[i][j][0][k];
											out[1][k] = corners[i][j][1][k];
											
											out[2][k] = corners[i][0][j][k];
											out[3][k] = corners[i][1][j][k];
											
											out[4][k] = corners[0][i][j][k];
											out[5][k] = corners[1][i][j][k];
										}
										out += 6;
									}
								}
								if (selected)
								{
									float x = c->endpoints[0][0][0];
									float y = c->endpoints[0][0][1];
									float size = 0.1f;
									float z = get_mesh_height( x, y, _cell_physical_height );
									
									out[0][0] = x - size;
									out[0][1] = y - size;
									out[0][2] = z;
									
									out[1][0] = x + size;
									out[1][1] = y + size;
									out[1][2] = z;
									
									out[2][0] = x + size;
									out[2][1] = y - size;
									out[2][2] = z;
									
									out[3][0] = x - size;
									out[3][1] = y + size;
									out[3][2] = z;
									
									memcpy( marker_colors + marker_line_count, marker_box_selected_colors, 28 * 4 );
									marker_line_count += 28;
								}
								else
								{
									memcpy( marker_colors + marker_line_count, marker_box_colors, 24 * 4 );
									marker_line_count += 24;
								}
							}
						}
					}
				}
			}
			
			glDisable( GL_ALPHA_TEST );
			glDisableClientState( GL_TEXTURE_COORD_ARRAY );
			glDisableClientState( GL_VERTEX_ARRAY );
		}
		
		glDisable( GL_TEXTURE_2D );
		glEnableClientState( GL_VERTEX_ARRAY );
		glEnableClientState( GL_COLOR_ARRAY );
		glVertexPointer( 3, GL_FLOAT, 0, marker_vectors );
		glColorPointer( 4, GL_UNSIGNED_BYTE, 0, marker_colors );
		if (q_glLockArraysEXT) q_glLockArraysEXT( 0, marker_line_count );
		glDrawArrays( GL_LINES, 0, marker_line_count );
		glEnable( GL_BLEND );
		glDisable( GL_DEPTH_TEST );
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		glDrawArrays( GL_LINES, 0, marker_line_count );
		glEnable( GL_DEPTH_TEST );
		glDisable( GL_BLEND );
		if (q_glUnlockArraysEXT) q_glUnlockArraysEXT();
		glDisableClientState( GL_COLOR_ARRAY );
		glDisableClientState( GL_VERTEX_ARRAY );
	}
	
	if (showTerrainType || form->currentMeshTool == _mesh_tool_terrain)
	{
		vec3 mesh_verts[17*17*6];
		unsigned mesh_colors[17*17*6];
		word mesh_mask[17][17];
		short mesh_index[16][17][17];
		short triangle_indices[16][16][2][3];
		int vcount;
		
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, sizeof(vec3), mesh_verts );
		glEnableClientState( GL_COLOR_ARRAY );
		glColorPointer( 4, GL_UNSIGNED_BYTE, 4, mesh_colors );
		glDisable( GL_TEXTURE_2D );
		glEnable( GL_POLYGON_OFFSET_FILL );
		glPolygonOffset( 1.0, 1.0 );
		glDepthFunc( GL_LEQUAL );
		if (showMeshPolys)
		{
			glEnable( GL_BLEND );
			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		}
		else
		{
			glDisable( GL_BLEND );
		}
		
		for (int sub_y = view_sy0; sub_y < view_sy1; ++sub_y)
		{
			for (int sub_x = view_sx0; sub_x < view_sx1; ++sub_x)
			{
				memset( mesh_mask, 0, sizeof(mesh_mask) );
				vcount = 0;
				
				int base_y = sub_y << 4;
				int base_x = sub_x << 4;
				for (int vy = 0; vy < 16; ++vy)
				{
					for (int vx = 0; vx < 16; ++vx)
					{
						mesh_cell *cell = form->get_cell( base_x + vx, base_y + vy );
						int type;
						
						if ((vx ^ vy) & 1)
						{
							type = GET_CELL_TERRAIN_TYPE(cell,0);
							triangle_indices[vy][vx][0][0] = get_terrain_vert_index( base_x, base_y, vx, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][0][1] = get_terrain_vert_index( base_x, base_y, vx, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][0][2] = get_terrain_vert_index( base_x, base_y, vx+1, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							
							type = GET_CELL_TERRAIN_TYPE(cell,1);
							triangle_indices[vy][vx][1][0] = get_terrain_vert_index( base_x, base_y, vx+1, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][1][1] = get_terrain_vert_index( base_x, base_y, vx, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][1][2] = get_terrain_vert_index( base_x, base_y, vx+1, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
						}
						else
						{
							type = GET_CELL_TERRAIN_TYPE(cell,0);
							triangle_indices[vy][vx][0][0] = get_terrain_vert_index( base_x, base_y, vx, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][0][1] = get_terrain_vert_index( base_x, base_y, vx, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][0][2] = get_terrain_vert_index( base_x, base_y, vx+1, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							
							type = GET_CELL_TERRAIN_TYPE(cell,1);
							triangle_indices[vy][vx][1][0] = get_terrain_vert_index( base_x, base_y, vx, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][1][1] = get_terrain_vert_index( base_x, base_y, vx+1, vy+1, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
							triangle_indices[vy][vx][1][2] = get_terrain_vert_index( base_x, base_y, vx+1, vy, type, mesh_verts, mesh_colors, mesh_mask, mesh_index, &vcount );
						}
					}
				}
				
				glDrawElements( GL_TRIANGLES, 16*16*6, GL_UNSIGNED_SHORT, triangle_indices );
			}
		}
		
		glDisableClientState( GL_VERTEX_ARRAY );
		glDisableClientState( GL_COLOR_ARRAY );
		glDisable( GL_POLYGON_OFFSET_FILL );
		glDisable( GL_BLEND );
	}
	
	glDisable( GL_TEXTURE_2D );
	glLineWidth( 1.0f );
	glEnableClientState( GL_VERTEX_ARRAY );
	glEnableClientState( GL_COLOR_ARRAY );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glDepthMask( GL_FALSE );
	glDepthFunc( GL_LEQUAL );
	
	if (meshHeightShown[_cell_render_height] || targetHeightKind == _cell_render_height)
	{
		vec3 mesh_verts[17][17];
		byte mesh_color[17][17][4];
		
		glVertexPointer( 3, GL_FLOAT, sizeof(vec3), mesh_verts );
		glColorPointer( 4, GL_UNSIGNED_BYTE, 4, mesh_color );
		
		for (int sub_y = view_sy0; sub_y < view_sy1; ++sub_y)
		{
			for (int sub_x = view_sx0; sub_x < view_sx1; ++sub_x)
			{
				int cy = sub_y << 4;
				float fcy = cy;
				for (int vy = 0; vy < 17; ++vy)
				{
					int cx = sub_x << 4;
					float fcx = cx;
					for (int vx = 0; vx < 17; ++vx)
					{
						mesh_verts[vy][vx].x = fcx;
						mesh_verts[vy][vx].y = fcy;
						mesh_cell *cell = form->get_cell( cx, cy );
						float h = 0.0f;
						if (cell)
						{
							h = cell->render_height * (1.0f / 512.0f);
							if (targetHeightKind == _cell_render_height && cellSelect.HasMember(cell))
							{
								mesh_color[vy][vx][0] = 0xff;
								mesh_color[vy][vx][1] = 0xff;
								mesh_color[vy][vx][2] = 0;
								mesh_color[vy][vx][3] = 0x30;
							}
							else
							{
								mesh_color[vy][vx][0] = 0x60;
								mesh_color[vy][vx][1] = 0x00;
								mesh_color[vy][vx][2] = 0x60;
								mesh_color[vy][vx][3] = 0x30;
							}
						}
						mesh_verts[vy][vx].z = h;
						++cx;
						fcx += 1.0f;
					}
					++cy;
					fcy += 1.0f;
				}
				
				int render_index_count = 0;
				unsigned short render_index[17*17*3*2];
				for (i = 0; i < 17; ++i)
				{
					unsigned short build_index[17*3];
					for (j = 0; j < 17; ++j)
					{
						build_index[j] = (i+((j^i)&1))*17+j;
						build_index[17+j] = i+j*17;
						build_index[17*2+j] = i*17+j;
					}
					for (j = (i < 16 ? 0 : 17); j < 17*3; ++j)
					{
						if (j == 17-1 || j == 2*17-1 || j == 3*17-1) continue;
						render_index[render_index_count++] = build_index[j];
						render_index[render_index_count++] = build_index[j+1];
					}
				}
				if (render_index_count)
				{
					if (q_glLockArraysEXT) q_glLockArraysEXT( 0, 17*17 );
					
					glDepthFunc( GL_LEQUAL );
					glDisable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					glDepthFunc( GL_GREATER );
					glEnable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					if (q_glUnlockArraysEXT) q_glUnlockArraysEXT();
				}
			}
		}
	}
	
	if (meshHeightShown[_cell_physical_height] || targetHeightKind == _cell_physical_height)
	{
		vec3 mesh_verts[17][17];
		byte mesh_color[17][17][4];
		bool draw_vert[17*17];
		
		glVertexPointer( 3, GL_FLOAT, sizeof(vec3), mesh_verts );
		glColorPointer( 4, GL_UNSIGNED_BYTE, 4, mesh_color );
		
		for (int sub_y = view_sy0; sub_y < view_sy1; ++sub_y)
		{
			for (int sub_x = view_sx0; sub_x < view_sx1; ++sub_x)
			{
				int cy = sub_y << 4;
				float fcy = cy;
				for (int vy = 0; vy < 17; ++vy)
				{
					int cx = sub_x << 4;
					float fcx = cx;
					for (int vx = 0; vx < 17; ++vx)
					{
						mesh_verts[vy][vx].x = fcx;
						mesh_verts[vy][vx].y = fcy;
						mesh_cell *cell = form->get_cell( cx, cy );
						float h = 0.0f;
						draw_vert[vy*17+vx] = false;
						if (cell)
						{
							h = cell->physical_height * (1.0f / 512.0f);
							if (targetHeightKind == _cell_physical_height && cellSelect.HasMember(cell))
							{
								mesh_color[vy][vx][0] = 0xff;
								mesh_color[vy][vx][1] = 0xff;
								mesh_color[vy][vx][2] = 0;
								mesh_color[vy][vx][3] = 0x30;
								draw_vert[vy*17+vx] = true;
							}
							else
							{
								mesh_color[vy][vx][0] = 0xc0;
								mesh_color[vy][vx][1] = 0x00;
								mesh_color[vy][vx][2] = 0x30;
								mesh_color[vy][vx][3] = 0x30;
								draw_vert[vy*17+vx] = !meshHeightShown[_cell_render_height] || cell->render_height != cell->physical_height;
							}
						}
						mesh_verts[vy][vx].z = h;
						++cx;
						fcx += 1.0f;
					}
					++cy;
					fcy += 1.0f;
				}
				
				int render_index_count = 0;
				unsigned short render_index[17*17*3*2];
				for (i = 0; i < 17; ++i)
				{
					unsigned short build_index[17*3];
					for (j = 0; j < 17; ++j)
					{
						build_index[j] = (i+((j^i)&1))*17+j;
						build_index[17+j] = i+j*17;
						build_index[17*2+j] = i*17+j;
					}
					for (j = (i < 16 ? 0 : 17); j < 17*3; ++j)
					{
						if (j == 17-1 || j == 2*17-1 || j == 3*17-1) continue;
						if (draw_vert[build_index[j]] || draw_vert[build_index[j+1]])
						{
							render_index[render_index_count++] = build_index[j];
							render_index[render_index_count++] = build_index[j+1];
						}
					}
				}
				if (render_index_count)
				{
					if (q_glLockArraysEXT) q_glLockArraysEXT( 0, 17*17 );
					
					glDepthFunc( GL_LEQUAL );
					glDisable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					glDepthFunc( GL_GREATER );
					glEnable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					if (q_glUnlockArraysEXT) q_glUnlockArraysEXT();
				}
			}
		}
	}
	
	if (meshHeightShown[_cell_media_height] || targetHeightKind == _cell_media_height)
	{
		vec3 mesh_verts[17*17];
		byte mesh_color[17*17][4];
		int draw_vert_index[17][17];
		
		glVertexPointer( 3, GL_FLOAT, sizeof(vec3), mesh_verts );
		glColorPointer( 4, GL_UNSIGNED_BYTE, 4, mesh_color );
		
		for (int sub_y = view_sy0; sub_y < view_sy1; ++sub_y)
		{
			for (int sub_x = view_sx0; sub_x < view_sx1; ++sub_x)
			{
				int draw_vert_count = 0;
				int cy = sub_y << 4;
				float fcy = cy;
				for (int vy = 0; vy < 17; ++vy)
				{
					int cx = sub_x << 4;
					float fcx = cx;
					for (int vx = 0; vx < 17; ++vx)
					{
						mesh_cell *cell = form->get_cell( cx, cy );
						draw_vert_index[vx][vy] = -1;
						if (cell && (cell->flags & _mesh_vertex_is_media_flag))
						{
							draw_vert_index[vx][vy] = draw_vert_count;
							mesh_verts[draw_vert_count].x = fcx;
							mesh_verts[draw_vert_count].y = fcy;
							mesh_verts[draw_vert_count].z = cell->media_height * (1.0f / 512.0f);
							if (targetHeightKind == _cell_media_height && cellSelect.HasMember(cell))
							{
								mesh_color[draw_vert_count][0] = 0xff;
								mesh_color[draw_vert_count][1] = 0xff;
								mesh_color[draw_vert_count][2] = 0;
								mesh_color[draw_vert_count][3] = 0x30;
							}
							else
							{
								mesh_color[draw_vert_count][0] = 0x00;
								mesh_color[draw_vert_count][1] = 0xc0;
								mesh_color[draw_vert_count][2] = 0xc0;
								mesh_color[draw_vert_count][3] = 0x30;
							}
							draw_vert_count += 1;
						}
						++cx;
						fcx += 1.0f;
					}
					++cy;
					fcy += 1.0f;
				}
				
				int render_index_count = 0;
				unsigned short render_index[17*17*3*2];
				cy = sub_y << 4;
				for (i = 0; i < 16; ++i, ++cy)
				{
					int cx = sub_x << 4;
					for (j = 0; j < 16; ++j, ++cx)
					{
						if (draw_vert_index[j][i] >= 0 || draw_vert_index[j+1][i] >= 0 ||
							draw_vert_index[j][i+1] >= 0 || draw_vert_index[j+1][i+1] >= 0)
						{
							struct mesh_cell *cell = form->get_cell( cx, cy );
							bool h = false, v = false;
							
							if (cell && AT_LEAST_PARTIALLY_MEDIA(cell))
							{
								if ((j^i)&1)
								{
									render_index[render_index_count++] = draw_vert_index[j+1][i];
									render_index[render_index_count++] = draw_vert_index[j][i+1];
								}
								else
								{
									render_index[render_index_count++] = draw_vert_index[j][i];
									render_index[render_index_count++] = draw_vert_index[j+1][i+1];
								}
								h = true;
								v = true;
							}
							else
							{
								cell = form->get_cell( cx, cy - 1 );
								if (cell && AT_LEAST_PARTIALLY_MEDIA(cell)) h = true;
								
								cell = form->get_cell( cx - 1, cy );
								if (cell && AT_LEAST_PARTIALLY_MEDIA(cell)) v = true;
							}
							
							if (h)
							{
								render_index[render_index_count++] = draw_vert_index[j][i];
								render_index[render_index_count++] = draw_vert_index[j+1][i];
							}
							
							if (v)
							{
								render_index[render_index_count++] = draw_vert_index[j][i];
								render_index[render_index_count++] = draw_vert_index[j][i+1];
							}
						}
					}
				}
				if (render_index_count)
				{
					if (q_glLockArraysEXT) q_glLockArraysEXT( 0, draw_vert_count );
					
					glDepthFunc( GL_LEQUAL );
					glDisable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					glDepthFunc( GL_GREATER );
					glEnable( GL_BLEND );
					glDrawElements( GL_LINES, render_index_count, GL_UNSIGNED_SHORT, render_index );
					
					if (q_glUnlockArraysEXT) q_glUnlockArraysEXT();
				}
			}
		}
	}
	
	
	glDisableClientState( GL_COLOR_ARRAY );
	glDisableClientState( GL_VERTEX_ARRAY );
	glDepthMask( GL_TRUE );
	
	if ((drag_mode == drag_mode_band_add || drag_mode == drag_mode_band_remove) &&
		(drag_screen_start.h != drag_screen_end.h || drag_screen_start.v != drag_screen_end.v))
	{
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		{
			XRect r = GetContentRect();
			gluOrtho2D( r.left, r.right, r.bottom, r.top );
		}
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity();
		glDepthFunc( GL_ALWAYS );
		glDepthMask( GL_FALSE );
		if (drag_mode == drag_mode_band_remove)
		{
			glColor3f( 1.0f, 0.0f, 0.0f );
		}
		else
		{
			glColor3f( 1.0f, 1.0f, 0.0f );
		}
		glLineWidth( 2.0f );
		glBegin( GL_LINE_LOOP );
			glVertex2i( drag_screen_start.h, drag_screen_start.v );
			glVertex2i( drag_screen_start.h, drag_screen_end.v );
			glVertex2i( drag_screen_end.h, drag_screen_end.v );
			glVertex2i( drag_screen_end.h, drag_screen_start.v );
		glEnd();
		glLineWidth( 1.0f );
		glDepthFunc( GL_LEQUAL );
		glDepthMask( GL_TRUE );
	}
	
	
	glFinish();
	this->ShowBackBuffer();
}


void TMeshView::get_affected_markers( XGPtrSet<struct marker_data> &out_markers, const XGPtrSet<struct mesh_cell> &cells )
{
	out_markers.Empty();
	for (int i = 0; i < form->markers.Length(); ++i)
	{
		marker_data *m = &(form->markers[i]);
		int cx = WORLD_TO_INTEGER( m->position.x );
		int cy = WORLD_TO_INTEGER( m->position.y );
		if (cells.HasMember( form->get_cell( cx, cy ) ) ||
			cells.HasMember( form->get_cell( cx+1, cy ) ) ||
			cells.HasMember( form->get_cell( cx, cy+1 ) ) ||
			cells.HasMember( form->get_cell( cx+1, cy+1 ) ))
		{
			out_markers.Add( m );
		}
	}
}


void TMeshView::cell_to_pos( int *out_x, int *out_y, const struct mesh_cell *cell )
{
	int offset = cell - form->get_cell( 0, 0 );
	int dy = offset / form->width;
	int dx = offset % form->width;
	if (out_y) *out_y = dy;
	if (out_x) *out_x = dx;
}


bool TMeshView::DoMouseDown( XPoint mouse_pt, short attrib )
{
	if (!form) return false;
	
	SetFocus();
	
	vec3 mouse_vec = point_to_vector( mouse_pt );
	vec3 mesh_intersect;
	vec3 marker_intersect;
	int cell_x, cell_y;
	int clicked_marker, clicked_axis;
	float mesh_dist, marker_dist;
	mesh_dist = marker_dist = 1000000.0f;
	bool any_clicked = false;
	
	drag_mode = drag_mode_none;
	dragged_marker = 0;
	dragged_cell = 0;
	
	baseCellSelect.Empty();
	baseMarkerSelect.Empty();
	
#if defined(OPT_WINOS)
	if (attrib & KKeyMenu)
#else
	if (attrib & KKeyCommand)
#endif
	{
		
		if ((form->currentMeshTool == _mesh_tool_band_grid || form->currentMeshTool == _mesh_tool_band_markers) &&
			line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &mesh_intersect, false, false, _cell_physical_height ))
		{
			cellSelect.Empty();
			markerSelect.Empty();
			InvalView();
			marker_data *m = form->CreateNewMarker();
			if (m)
			{
				m->position.x = FLOAT_TO_WORLD( mesh_intersect.x );
				m->position.y = FLOAT_TO_WORLD( mesh_intersect.y );
				m->position.z = FLOAT_TO_WORLD( mesh_intersect.z );
				m->height = 0;
				ResetMarkers();
				markerSelect.Add( m );
				drag_mode = drag_mode_marker_xy;
				dragged_marker = m;
				marker_drag_start.DeleteAll();
				{
					world_location3d loc;
					loc.position = m->position;
					loc.velocity = m->velocity;
					loc.height = m->height;
					loc.yaw = m->yaw;
					loc.pitch = m->pitch;
					loc.roll = m->roll;
					marker_drag_start += loc;
				}
				
				drag_anchor_point = mesh_intersect;
				drag_axis = vec3(0,0,0);
				drag_mode = drag_mode_marker_xy;
				dragged_handle = 1;
				UpdateStatusBar();
				return true;
			}
		}
		
		if ((form->currentMeshTool == _mesh_tool_terrain) &&
			line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &mesh_intersect, false, false, _cell_render_physical_max ))
		{
			cell_x = (int)floor(mesh_intersect.x);
			cell_y = (int)floor(mesh_intersect.y);
			int tri = ((cell_x ^ cell_y) & 1) ? (cell_x + 1 - mesh_intersect.x > mesh_intersect.y - cell_y ? 2 : 3) : (mesh_intersect.x - cell_x < mesh_intersect.y - cell_y ? 0 : 1);
			mesh_cell *cell = form->get_cell( cell_x, cell_y );
			if (cell)
			{
				int old_type = GET_CELL_TERRAIN_TYPE( cell, (tri & 1) );
				if (old_type != form->targetTerrainType)
				{
					flood_fill_terrain( cell_x, cell_y, tri, old_type, form->targetTerrainType );
				}
				return false;
			}
		}
		
		if ((form->currentMeshTool == _mesh_tool_water) &&
			line_intersects_mesh( form->view_origin, mouse_vec, &cell_x, &cell_y, &mesh_intersect, false, false, _cell_media_height ))
		{
			cell_x = (int)floor(mesh_intersect.x);
			cell_y = (int)floor(mesh_intersect.y);
			struct mesh_cell *cell = form->get_cell( cell_x, cell_y );
			if (AT_LEAST_PARTIALLY_MEDIA(cell))
			{
				if (cell->media_height == targetMediaHeight &&
					(cell_x >= form->width - 1 || form->get_cell( cell_x + 1, cell_y )->media_height == targetMediaHeight) &&
					(cell_y >= form->height - 1 || form->get_cell( cell_x, cell_y + 1 )->media_height == targetMediaHeight) &&
					(cell_x >= form->width - 1 || cell_y >= form->height - 1 || form->get_cell( cell_x + 1, cell_y + 1 )->media_height == targetMediaHeight))
				{
					flood_fill_water( cell_x, cell_y, true, false );
				}
				else
				{
					flood_fill_water( cell_x, cell_y, true, true );
				}
			}
			else
			{
				flood_fill_water( cell_x, cell_y, false, true );
			}
			return false;
		}
		
		return false;
	}
	else
	{
		if (form->currentMeshTool != _mesh_tool_pull_grid &&
			form->currentMeshTool != _mesh_tool_smooth_grid &&
			targetHeightKind >= 0 &&
			line_intersects_mesh( form->view_origin, mouse_vec, &cell_x, &cell_y, &mesh_intersect, true, true, targetHeightKind ))
		{
			any_clicked = true;
			mesh_dist = dot( mesh_intersect - form->view_origin, form->view_forward );
		}
		
		if (markerViewMode != KMenuMeshMarkersOff &&
			line_intersects_marker( form->view_origin, mouse_vec, &clicked_marker, &clicked_axis, &marker_intersect ))
		{
			any_clicked = true;
			marker_dist = dot( marker_intersect - form->view_origin, form->view_forward );
		}
		
		if (!any_clicked)
		{
			if (form->currentMeshTool == _mesh_tool_band_grid || form->currentMeshTool == _mesh_tool_band_markers ||
				(attrib & KKeyShift) || (attrib & KKeyControl))
			{
				if ((attrib & KKeyShift) || (attrib & KKeyControl))
				{
					if (form->currentMeshTool == _mesh_tool_band_grid)
					{
						baseCellSelect = cellSelect;
					}
					else if (form->currentMeshTool == _mesh_tool_band_markers)
					{
						baseMarkerSelect = markerSelect;
					}
				}
				drag_mode = drag_mode_band_add;
				if (attrib & KKeyControl)
				{
					drag_mode = drag_mode_band_remove;
				}
				drag_screen_start = drag_screen_end = mouse_pt;
				cellSelect = baseCellSelect;
				markerSelect = baseMarkerSelect;
				InvalView();
				return true;
			}
			else if (form->currentMeshTool == _mesh_tool_water)
			{
				if (line_intersects_mesh( form->view_origin, mouse_vec, &cell_x, &cell_y, &mesh_intersect, false, false, _cell_media_height ))
				{
					struct mesh_cell *cell = form->get_cell( cell_x, cell_y );
					if (AT_LEAST_PARTIALLY_MEDIA( cell ) &&
						cell->media_height == targetMediaHeight &&
						(cell_x >= form->width - 1 || form->get_cell( cell_x + 1, cell_y )->media_height == targetMediaHeight) &&
						(cell_y >= form->height - 1 || form->get_cell( cell_x, cell_y + 1 )->media_height == targetMediaHeight) &&
						(cell_x >= form->width - 1 || cell_y >= form->height - 1 || form->get_cell( cell_x + 1, cell_y + 1 )->media_height == targetMediaHeight))
					{
						drag_mode = drag_mode_water_clear;
					}
					else
					{
						drag_mode = drag_mode_water_set;
					}
					drag_anchor_point = mesh_intersect;
					DoMouseMove( mouse_pt, (attrib | KCapturedMouse) );
					return true;
				}
			}
			else if (form->currentMeshTool == _mesh_tool_terrain)
			{
				if (line_intersects_mesh( form->view_origin, mouse_vec, &cell_x, &cell_y, &mesh_intersect, false, false, _cell_render_physical_max ))
				{
					drag_mode = drag_mode_terrain_paint;
					drag_anchor_point = mesh_intersect;
					DoMouseMove( mouse_pt, (attrib | KCapturedMouse) );
					return true;
				}
			}
			else if ((form->currentMeshTool == _mesh_tool_pull_grid || form->currentMeshTool == _mesh_tool_smooth_grid) &&
					 targetHeightKind >= 0)
			{
				if (line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &drag_anchor_point, false, false, targetHeightKind ))
				{
					ClearSelection();
					cell_drag_start.DeleteAll();
					soft_brush_level.DeleteAll();
					int y0 = floor( drag_anchor_point.y - softBrushRadius );
					int y1 = ceil( drag_anchor_point.y + softBrushRadius );
					int x0 = floor( drag_anchor_point.x - softBrushRadius );
					int x1 = ceil( drag_anchor_point.x + softBrushRadius );
					float totalHeights[MAXIMUM_CELL_HEIGHTS];
					float totalWeight = 0.0f;
					for (int i = 0; i < MAXIMUM_CELL_HEIGHTS; ++i)
					{
						totalHeights[i] = 0.0f;
					}
					for (int y = y0; y <= y1; ++y)
					{
						for (int x = x0; x <= x1; ++x)
						{
							struct mesh_cell *c = form->get_cell( x, y );
							if (c)
							{
								float d = (x - drag_anchor_point.x) * (x - drag_anchor_point.x) + (y - drag_anchor_point.y) * (y - drag_anchor_point.y);
								if (d < softBrushRadius * softBrushRadius)
								{
									d = get_soft_brush_level( sqrtf(d) );
									cellSelect.Add( c );
									soft_brush_level += d;
									cell_drag_start += *c;
									totalWeight += d;
									for (int i = 0; i < MAXIMUM_CELL_HEIGHTS; ++i)
									{
										totalHeights[i] += cell_float_height( c, i ) * d;
									}
								}
							}
						}
					}
					if (totalWeight > 0.0f)
					{
						for (int i = 0; i < MAXIMUM_CELL_HEIGHTS; ++i)
						{
							mutable_cell_world_height( &this->smooth_target_heights, i ) = FLOAT_TO_WORLD( totalHeights[i] / totalWeight );
						}
						InvalView();
						drag_axis = vec3( 0, 0, 1 );
						if (form->currentMeshTool == _mesh_tool_smooth_grid)
						{
							drag_mode = drag_mode_smooth_grid;
						}
						else
						{
							drag_mode = drag_mode_pull_grid;
						}
						get_affected_markers( baseMarkerSelect, cellSelect );
						return true;
					}
				}
			}
			
			ClearSelection();
			return false;
		}
		
		
		if (mesh_dist < marker_dist)
		{
			markerSelect.Empty();
			
			mesh_cell *cell = form->get_cell( cell_x, cell_y );
			if (!cellSelect.HasMember( cell ))
			{
				if (!(attrib & KKeyShift))
				{
					cellSelect.Empty();
				}
				cellSelect.Add( cell );
			}
			else
			{
				if (attrib & KKeyShift)
				{
					cellSelect.Remove( cell );
					cell = 0;
				}
			}
			
			if (cell)
			{
				drag_anchor_point = vec3( cell_x, cell_y, cell_float_height( cell, targetHeightKind ) );
				drag_axis = vec3( 0, 0, 1 );
				drag_anchor_point += distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec ) * drag_axis;
				last_drag_delta = 0;
				drag_mode = drag_mode_mesh_vertices;
				dragged_cell = cell;
				get_affected_markers( baseMarkerSelect, cellSelect );
			}
		}
		else
		{
			cellSelect.Empty();
			
			marker_data *m = &(form->markers[clicked_marker]);
			cached_marker_data *c = &(marker_cache[clicked_marker]);
			
			if (attrib & KDoubleClick)
			{
				marker_info_dlog_data midd;
				midd.m = m;
				midd.view = this;
				midd.form = this->form;
				if (XGDialog::ModalDialog( KWindowMarkerEdit, MarkerInfoProc, &midd ))
				{
					form->fModified = true;
					MarkerChanged( clicked_marker );
				}
				dragged_marker = m;
				UpdateStatusBar();
			}
			else
			{
				if (!markerSelect.HasMember( m ))
				{
					if (!(attrib & KKeyShift))
					{
						markerSelect.Empty();
					}
					markerSelect.Add( m );
				}
				else
				{
					if (attrib & KKeyShift)
					{
						markerSelect.Remove( m );
						clicked_marker = -1;
					}
				}
				
				marker_drag_start.DeleteAll();
				for (int i = 0; i < markerSelect.Size(); ++i)
				{
					world_location3d loc;
					loc.position = markerSelect[i]->position;
					loc.velocity = markerSelect[i]->velocity;
					loc.height = markerSelect[i]->height;
					loc.yaw = markerSelect[i]->yaw;
					loc.pitch = markerSelect[i]->pitch;
					loc.roll = markerSelect[i]->roll;
					marker_drag_start += loc;
				}
				
				if (clicked_marker >= 0)
				{
					drag_anchor_point = *(vec3 *)(c->endpoints[0][0]);
					dragged_marker = m;
					if (clicked_axis >= 0)
					{
						dragged_handle = clicked_axis + 2;
						if (attrib & KKeyControl)
						{
							if (!markerWorldAxis)
							{
								drag_mode = drag_mode_marker_rotate;
								switch (clicked_axis) {
									case 0: // yaw
										drag_rotate_plane_normal = vec3( 0, 0, 1 );
										break;
									case 1: // pitch
										drag_rotate_plane_normal = -normalized( cross( *(vec3 *)(c->mat[1]), vec3( 0, 0, 1 ) ) );
										break;
									case 2: // roll
										drag_rotate_plane_normal = -*(vec3 *)(c->mat[1]);
										break;
									default:
										drag_mode = drag_mode_none;
										break;
								}
								drag_rotate_plane_constant = -dot( drag_rotate_plane_normal, drag_anchor_point );
								
								drag_axis = form->view_origin + line_intersects_plane( form->view_origin, mouse_vec, drag_rotate_plane_normal, drag_rotate_plane_constant ) * mouse_vec;
								drag_axis = normalized( drag_axis - drag_anchor_point );
							}
						}
						else
						{
							drag_axis = normalized( *(vec3 *)(c->endpoints[clicked_axis][1]) - drag_anchor_point );
							drag_anchor_point += distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec ) * drag_axis;
							drag_mode = drag_mode_marker_axis;
						}
					}
					else
					{
						drag_axis.x = drag_anchor_point.x;
						drag_axis.y = drag_anchor_point.y;
						drag_axis.z = get_mesh_height( drag_anchor_point.x, drag_anchor_point.y, _cell_physical_height );
						float dist_to_plane = line_intersects_plane( form->view_origin, mouse_vec, vec3( 0, 0, 1 ), -drag_anchor_point.z );
						drag_anchor_point = form->view_origin + dist_to_plane * mouse_vec;
						drag_axis = drag_axis - drag_anchor_point;
						drag_mode = drag_mode_marker_xy;
						dragged_handle = 1;
					}
				}
			}
		}
		
		InvalView();
		UpdateStatusBar();
		return drag_mode != drag_mode_none;
	}
}


void TMeshView::paint_line( vec3 start, vec3 end, void (TMeshView::*paint_func)( int cell_x, int cell_y, int triangle ) )
{
	float dx = end.x - start.x;
	float dy = end.y - start.y;
	int cell_x = (int)floor(start.x);
	int cell_y = (int)floor(start.y);
	int tri = ((cell_x ^ cell_y) & 1) ? (cell_x + 1 - start.x > start.y - cell_y ? 2 : 3) : (start.x - cell_x < start.y - cell_y ? 0 : 1);
	float old_t = 0.0f;
	
	for (;;)
	{
		(this->*paint_func)( cell_x, cell_y, tri );
		
		int next_cell_x, next_cell_y;
		int next_tri = -1;
		float t = 1.001f;
		float test;
		
		if (dx < 0.0f)
		{
			test = (cell_x - start.x) / dx;
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x - 1;
				next_cell_y = cell_y;
				next_tri = ((next_cell_x ^ next_cell_y) & 1) ? 3 : 1;
			}
		}
		
		if (dx > 0.0f)
		{
			test = (cell_x + 1 - start.x) / dx;
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x + 1;
				next_cell_y = cell_y;
				next_tri = ((next_cell_x ^ next_cell_y) & 1) ? 2 : 0;
			}
		}
		
		if (dy < 0.0f)
		{
			test = (cell_y - start.y) / dy;
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x;
				next_cell_y = cell_y - 1;
				next_tri = ((next_cell_x ^ next_cell_y) & 1) ? 3 : 0;
			}
		}
		
		if (dy > 0.0f)
		{
			test = (cell_y + 1 - start.y) / dy;
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x;
				next_cell_y = cell_y + 1;
				next_tri = ((next_cell_x ^ next_cell_y) & 1) ? 2 : 1;
			}
		}
		
	#if 1
		if ((dx > dy && tri == 0) || (dx < dy && tri == 1))
		{
			test = (cell_x - cell_y - start.x + start.y) / (dx - dy);
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x;
				next_cell_y = cell_y;
				next_tri = tri ^ 1;
			}
		}
		
		if ((dx > -dy && tri == 2) || (dx < -dy && tri == 3))
		{
			test = (cell_x + cell_y + 1 - start.x - start.y) / (dx + dy);
			//assert( test > old_t );
			if (test < t)
			{
				t = test;
				next_cell_x = cell_x;
				next_cell_y = cell_y;
				next_tri = tri ^ 1;
			}
		}
	#endif
		
		if (next_tri == -1) break;
		cell_x = next_cell_x;
		cell_y = next_cell_y;
		tri = next_tri;
		old_t = t;
	}
}


void TMeshView::set_terrain( int cell_x, int cell_y, int triangle )
{
	mesh_cell *cell = form->get_cell( cell_x, cell_y );
	if (cell)
	{
		triangle = ((triangle & 1) ^ 1) * TERRAIN_TYPE_BITS;
		cell->flags = (cell->flags & ~(TERRAIN_TYPE_MASK << triangle)) |
						(form->targetTerrainType << triangle);
	}
}


void TMeshView::flood_fill_terrain( int cell_x, int cell_y, int triangle, int source_type, int dest_type )
{
	if (source_type == dest_type) return;
	
	int stack_size = 0;
	int max_stack = 4*form->width*form->height;
	short (*stack)[3] = new short[max_stack][3];
	if (!stack) return;
	
	for (;;)
	{
		assert( (triangle >> 1) == ((cell_x ^ cell_y) & 1) );
		
		struct mesh_cell *cell = form->get_cell( cell_x, cell_y );
		if (cell)
		{
			int shift = ((triangle & 1) ^ 1) * TERRAIN_TYPE_BITS;
			if (((cell->flags >> shift) & TERRAIN_TYPE_MASK) == source_type)
			{
				cell->flags = (cell->flags & ~(TERRAIN_TYPE_MASK << shift)) | (dest_type << shift);
				
				if (stack_size + 3 < max_stack)
				{
					if (triangle == 0 || triangle == 2)
					{
						stack[stack_size][0] = cell_x - 1;
					}
					else //if (triangle == 1 || triangle == 3)
					{
						stack[stack_size][0] = cell_x + 1;
					}
					stack[stack_size][1] = cell_y;
					stack[stack_size][2] = triangle ^ 3;
					++stack_size;
					
					if (triangle == 1 || triangle == 2)
					{
						stack[stack_size][1] = cell_y - 1;
					}
					else //if (triangle == 0 || triangle == 3)
					{
						stack[stack_size][1] = cell_y + 1;
					}
					stack[stack_size][0] = cell_x;
					stack[stack_size][2] = triangle ^ 2;
					++stack_size;
					
					stack[stack_size][0] = cell_x;
					stack[stack_size][1] = cell_y;
					stack[stack_size][2] = triangle ^ 1;
					++stack_size;
				}
			}
		}
		
		if (stack_size <= 0) break;
		--stack_size;
		cell_x = stack[stack_size][0];
		cell_y = stack[stack_size][1];
		triangle = stack[stack_size][2];
	}
	
	InvalView();
	form->fModified = true;
	
	delete [] stack;
}


void TMeshView::flood_fill_water( int cell_x, int cell_y, bool src_wet, int dest_wet )
{
	if (!src_wet && !dest_wet) return;
	
	int stack_size = 0;
	int max_stack = 4*form->width*form->height;
	short (*stack)[2] = new short[max_stack][2];
	if (!stack) return;
	
	for (;;)
	{
		assert( (triangle >> 1) == ((cell_x ^ cell_y) & 1) );
		
		struct mesh_cell *cell = form->get_cell( cell_x, cell_y );
		if (cell)
		{
			bool changed = false;
			if (AT_LEAST_PARTIALLY_MEDIA(cell))
			{
				if (src_wet)
				{
					mesh_cell *c01 = form->get_cell( cell_x, cell_y + 1 );
					mesh_cell *c10 = form->get_cell( cell_x + 1, cell_y );
					mesh_cell *c11 = form->get_cell( cell_x + 1, cell_y + 1 );
					if (dest_wet)
					{
						if (cell->media_height != targetMediaHeight ||
							(!c01 || c01->media_height != targetMediaHeight) ||
							(!c10 || c10->media_height != targetMediaHeight) ||
							(!c11 || c11->media_height != targetMediaHeight))
						{
							set_water( cell_x, cell_y, -1 );
							changed = true;
						}
					}
					else
					{
						clear_water( cell_x, cell_y, -1 );
						changed = true;
					}
				}
			}
			else
			{
				if (!src_wet && dest_wet)
				{
					set_water( cell_x, cell_y, -1 );
					changed = true;
				}
			}
			
			if (changed && stack_size + 4 < max_stack)
			{
				stack[stack_size][0] = cell_x + 1;
				stack[stack_size][1] = cell_y;
				++stack_size;
				
				stack[stack_size][0] = cell_x - 1;
				stack[stack_size][1] = cell_y;
				++stack_size;
				
				stack[stack_size][0] = cell_x;
				stack[stack_size][1] = cell_y + 1;
				++stack_size;
				
				stack[stack_size][0] = cell_x;
				stack[stack_size][1] = cell_y - 1;
				++stack_size;
			}
		}
		
		if (stack_size <= 0) break;
		--stack_size;
		cell_x = stack[stack_size][0];
		cell_y = stack[stack_size][1];
	}
	
	InvalView();
	form->fModified = true;
	
	delete [] stack;
}


void TMeshView::clear_water( int cell_x, int cell_y, int /* triangle */ )
{
	mesh_cell *cell = form->get_cell( cell_x, cell_y );
	if (cell)
	{
		cell->flags &= ~(_mesh_cell_triangle0_is_media_flag|_mesh_cell_triangle1_is_media_flag);
		form->fix_vertex_media_flag( cell_x, cell_y );
		form->fix_vertex_media_flag( cell_x + 1, cell_y );
		form->fix_vertex_media_flag( cell_x, cell_y + 1 );
		form->fix_vertex_media_flag( cell_x + 1, cell_y + 1 );
	}
}


void TMeshView::set_water( int cell_x, int cell_y, int /* triangle */ )
{
	mesh_cell *cell = form->get_cell( cell_x, cell_y );
	if (cell)
	{
		cell->flags |= _mesh_cell_triangle0_is_media_flag|_mesh_cell_triangle1_is_media_flag|_mesh_vertex_is_media_flag;
		cell->media_height = targetMediaHeight;
		
		if ((cell = form->get_cell( cell_x + 1, cell_y )) != 0)
		{
			cell->media_height = targetMediaHeight;
			cell->flags |= _mesh_vertex_is_media_flag;
		}
		
		if ((cell = form->get_cell( cell_x, cell_y + 1 )) != 0)
		{
			cell->media_height = targetMediaHeight;
			cell->flags |= _mesh_vertex_is_media_flag;
		}
		
		if ((cell = form->get_cell( cell_x + 1, cell_y + 1 )) != 0)
		{
			cell->media_height = targetMediaHeight;
			cell->flags |= _mesh_vertex_is_media_flag;
		}
	}
}


void TMeshView::adjust_markers_over_changed_cells( const XGPtrSet<struct marker_data> &markers )
{
	if (meshHeightMask[_cell_physical_height])
	{
		if (moveMarkersWithMesh)
		{
			for (int i = 0; i < markers.Size(); ++i)
			{
				marker_data *m = markers[i];
				m->position.z = FLOAT_TO_WORLD(get_mesh_height( WORLD_TO_FLOAT(m->position.x), WORLD_TO_FLOAT(m->position.y), _cell_physical_height )) + m->height;
				MarkerChanged( m - &(form->markers[0]) );
			}
		}
		else
		{
			for (int i = 0; i < markers.Size(); ++i)
			{
				marker_data *m = markers[i];
				m->height = m->position.z - FLOAT_TO_WORLD(get_mesh_height( WORLD_TO_FLOAT(m->position.x), WORLD_TO_FLOAT(m->position.y), _cell_physical_height ));
				MarkerChanged( m - &(form->markers[0]) );
			}
		}
	}
}


void TMeshView::DoMouseMove( XPoint mouse_pt, short attrib )
{
	vec3 mouse_vec = point_to_vector( mouse_pt );
	int i, j;
	
	if (attrib & KCapturedMouse)
	{
		if (drag_mode == drag_mode_pull_grid)
		{
			float elev_delta = distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec );
			for (i = 0; i < cellSelect.Size(); ++i)
			{
				for (j = 0; j < MAXIMUM_CELL_HEIGHTS; ++j)
				{
					if (meshHeightMask[j])
					{
						mutable_cell_world_height( cellSelect[i], j ) = cell_world_height( &(cell_drag_start[i]), j ) +
													FLOAT_TO_WORLD( elev_delta * soft_brush_level[i] );
					}
				}
			}
			adjust_markers_over_changed_cells( baseMarkerSelect );
			form->fModified = true;
			InvalView();
			UpdateStatusBar();
		}
		
		if (drag_mode == drag_mode_smooth_grid)
		{
			float smooth_delta = distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec ) / (1.5f * softBrushRadius);
			for (i = 0; i < cellSelect.Size(); ++i)
			{
				float d = smooth_delta * soft_brush_level[i];
				if (d > 1.0f) d = 1.0f;
				for (j = 0; j < MAXIMUM_CELL_HEIGHTS; ++j)
				{
					if (meshHeightMask[j])
					{
						mutable_cell_world_height( cellSelect[i], j ) = cell_world_height( &(cell_drag_start[i]), j ) +
													d * (cell_world_height( &smooth_target_heights, j ) - cell_world_height( &(cell_drag_start[i]), j ));
					}
				}
			}
			adjust_markers_over_changed_cells( baseMarkerSelect );
			form->fModified = true;
			InvalView();
			UpdateStatusBar();
		}
		
		if (drag_mode == drag_mode_mesh_vertices)
		{
			int elev_delta = (int)(distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec ) * 512);
			int delta_delta = elev_delta - last_drag_delta;
			last_drag_delta = elev_delta;
			for (i = 0; i < cellSelect.Size(); ++i)
			{
				for (j = 0; j < MAXIMUM_CELL_HEIGHTS; ++j)
				{
					if (meshHeightMask[j])
					{
						mutable_cell_world_height( cellSelect[i], j ) += delta_delta;
					}
				}
			}
			if (targetHeightKind == _cell_media_height && dragged_cell && cellSelect.HasMember(dragged_cell))
			{
				form->SetWorldDistance( 20, dragged_cell->media_height );
				targetMediaHeight = dragged_cell->media_height;
			}
			adjust_markers_over_changed_cells( baseMarkerSelect );
			form->fModified = true;
			InvalView();
			UpdateStatusBar();
		}
		
		if (drag_mode == drag_mode_marker_axis)
		{
			float dist = distance_along_line( drag_anchor_point, drag_axis, form->view_origin, mouse_vec );
			int dx = (int)(512.0 * drag_axis.x * dist );
			int dy = (int)(512.0 * drag_axis.y * dist );
			int dz = (int)(512.0 * drag_axis.z * dist );
			for (i = 0; i < markerSelect.Size(); ++i)
			{
				marker_data *m = markerSelect[i];
				m->position.x = marker_drag_start[i].position.x + dx;
				m->position.y = marker_drag_start[i].position.y + dy;
				m->position.z = marker_drag_start[i].position.z + dz;
				m->height = m->position.z - (int)(get_mesh_height( m->position.x / 512.0, m->position.y / 512.0, _cell_physical_height ) * 512.0);
				MarkerChanged( m - &(form->markers[0]) );
			}
			form->fModified = true;
			InvalView();
			UpdateStatusBar();
		}
		
		if (drag_mode == drag_mode_marker_rotate)
		{
			vec3 new_axis = form->view_origin + line_intersects_plane( form->view_origin, mouse_vec, drag_rotate_plane_normal, drag_rotate_plane_constant ) * mouse_vec;
			new_axis = normalized( new_axis - drag_anchor_point );
			float dx = dot( new_axis, drag_axis );
			float dy = dot( drag_rotate_plane_normal, cross( drag_axis, new_axis ));
			float theta = atan2( dy, dx );
			int dt = (int)(theta * HALF_CIRCLE / 3.1415926535);
			for (i = 0; i < markerSelect.Size(); ++i)
			{
				marker_data *m = markerSelect[i];
				switch (dragged_handle-2) {
					case 0:
						m->yaw = marker_drag_start[i].yaw + dt;
						break;
					case 1:
						m->pitch = marker_drag_start[i].pitch + dt;
						break;
					case 2:
						m->roll = marker_drag_start[i].roll + dt;
						break;
				}
				MarkerChanged( m - &(form->markers[0]) );
			}
			form->fModified = true;
			InvalView();
			UpdateStatusBar();
		}
		
		if (drag_mode == drag_mode_marker_xy)
		{
			vec3 pt_on_mesh;
			bool on_mesh = line_intersects_mesh( form->view_origin + drag_axis, mouse_vec, 0, 0, &pt_on_mesh, false, false, _cell_physical_height );
			if (on_mesh)
			{
				vec3 delta = (pt_on_mesh - drag_axis) - drag_anchor_point;
				int dx = (int)(512.0 * delta.x);
				int dy = (int)(512.0 * delta.y);
				int dz = (int)(512.0 * delta.z);
				for (i = 0; i < markerSelect.Size(); ++i)
				{
					marker_data *m = markerSelect[i];
					m->position.x = marker_drag_start[i].position.x + dx;
					m->position.y = marker_drag_start[i].position.y + dy;
					m->position.z = (int)(get_mesh_height( m->position.x / 512.0, m->position.y / 512.0, _cell_physical_height ) * 512.0) + m->height;
					MarkerChanged( m - &(form->markers[0]) );
				}
				form->fModified = true;
				InvalView();
				UpdateStatusBar();
			}
		}
		
		if (drag_mode == drag_mode_band_add || drag_mode == drag_mode_band_remove)
		{
			XPoint topleft, topright, bottomleft, bottomright;
			topleft.h = bottomleft.h = MIN( drag_screen_start.h, mouse_pt.h );
			topright.h = bottomright.h = MAX( drag_screen_start.h, mouse_pt.h );
			topleft.v = topright.v = MIN( drag_screen_start.v, mouse_pt.v );
			bottomleft.v = bottomright.v = MAX( drag_screen_start.v, mouse_pt.v );
			
			vec3 top, left, bottom, right;
			top = cross( point_to_vector( topleft ), point_to_vector( topright ) );
			right = cross( point_to_vector( topright ), point_to_vector( bottomright ) );
			bottom = cross( point_to_vector( bottomright ), point_to_vector( bottomleft ) );
			left = cross( point_to_vector( bottomleft ), point_to_vector( topleft ) );
			float ptop = dot( top, form->view_origin );
			float pleft = dot( left, form->view_origin );
			float pright = dot( right, form->view_origin );
			float pbottom = dot( bottom, form->view_origin );
			
			if (form->currentMeshTool == _mesh_tool_band_grid)
			{
				XGPtrSet<struct mesh_cell> inside;
				struct mesh_cell *cell = form->cells;
				vec3 pt;
				for (int y = 0; y < mesh_height; ++y)
				{
					pt.y = y;
					pt.x = 0;
					for (int x = 0; x < mesh_width; ++x, ++cell, pt.x += 1.0f)
					{
						if (targetHeightKind != _cell_media_height || AT_LEAST_PARTIALLY_MEDIA( cell ) ||
							(x > 0 && AT_LEAST_PARTIALLY_MEDIA( cell - 1 )) ||
							(y > 0 && AT_LEAST_PARTIALLY_MEDIA( cell - mesh_width )) ||
							(x > 0 && y > 0 && AT_LEAST_PARTIALLY_MEDIA( cell - mesh_width - 1)))
						{
							pt.z = cell_float_height( cell, targetHeightKind );
							if (dot( top, pt ) > ptop &&
								dot( left, pt ) > pleft &&
								dot( right, pt ) > pright &&
								dot( bottom, pt ) > pbottom)
							{
								inside += cell;
							}
						}
					}
				}
				
				cellSelect = baseCellSelect;
				if (drag_mode == drag_mode_band_add)
				{
					cellSelect |= inside;
				}
				else
				{
					cellSelect -= inside;
				}
			}
			else
			{
				XGPtrSet<struct marker_data> inside;
				inside.Empty();
				struct marker_data *m = &(form->markers[0]);
				int count = form->markers.Length();
				for (int i = 0; i < count; ++i, ++m)
				{
					vec3 pt;
					pt.x = WORLD_TO_FLOAT( m->position.x );
					pt.y = WORLD_TO_FLOAT( m->position.y );
					pt.z = WORLD_TO_FLOAT( m->position.z );
					if (dot( top, pt ) > ptop &&
						dot( left, pt ) > pleft &&
						dot( right, pt ) > pright &&
						dot( bottom, pt ) > pbottom)
					{
						inside += m;
					}
				}
				
				markerSelect = baseMarkerSelect;
				if (drag_mode == drag_mode_band_add)
				{
					markerSelect |= inside;
				}
				else
				{
					markerSelect &= baseMarkerSelect ^ inside;
				}
			}
			
			drag_screen_end = mouse_pt;
			InvalView();
		}
		
		if (drag_mode == drag_mode_water_set || drag_mode == drag_mode_water_clear)
		{
			vec3 mesh_pos;
			
			if (line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &mesh_pos, false, false, _cell_media_height ))
			{
				if (drag_mode == drag_mode_water_clear)
				{
					paint_line( drag_anchor_point, mesh_pos, clear_water );
				}
				else
				{
					paint_line( drag_anchor_point, mesh_pos, set_water );
				}
				drag_anchor_point = mesh_pos;
				InvalView();
				form->fModified = true;
			}
		}
		
		if (drag_mode == drag_mode_terrain_paint)
		{
			vec3 mesh_pos;
			
			if (line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &mesh_pos, false, false, _cell_render_physical_max ))
			{
				paint_line( drag_anchor_point, mesh_pos, set_terrain );
				drag_anchor_point = mesh_pos;
				InvalView();
				form->fModified = true;
			}
		}
	}
	else // mouse is not captured
	{
		vec3 mouse_vec = point_to_vector( mouse_pt );
		
		if (targetHeightKind >= 0 &&
			(form->currentMeshTool == _mesh_tool_pull_grid || form->currentMeshTool == _mesh_tool_smooth_grid) &&
			line_intersects_mesh( form->view_origin, mouse_vec, NULL, NULL, &drag_anchor_point, false, false, targetHeightKind ))
		{
			cellSelect.Empty();
			int y0 = floor( drag_anchor_point.y - softBrushRadius );
			int y1 = ceil( drag_anchor_point.y + softBrushRadius );
			int x0 = floor( drag_anchor_point.x - softBrushRadius );
			int x1 = ceil( drag_anchor_point.x + softBrushRadius );
			for (int y = y0; y <= y1; ++y)
			{
				for (int x = x0; x <= x1; ++x)
				{
					struct mesh_cell *c = form->get_cell( x, y );
					if (c)
					{
						float d = (x - drag_anchor_point.x) * (x - drag_anchor_point.x) + (y - drag_anchor_point.y) * (y - drag_anchor_point.y);
						if (d < softBrushRadius * softBrushRadius)
						{
							cellSelect.Add( c );
						}
					}
				}
			}
			InvalView();
		}
	}
}


void TMeshView::DoMouseUp( XPoint /* mouse_pt */, short /* attrib */ )
{
	if (!form) return;
	
	baseCellSelect.Empty();
	baseMarkerSelect.Empty();
	
	if (drag_mode != drag_mode_none)
	{
		InvalView();
	}
	
	drag_mode = drag_mode_none;
}


void TMeshView::SetMarkerWorldAxis( bool newMode )
{
	if (newMode == markerWorldAxis) return;
	markerWorldAxis = newMode;
	ResetMarkers();
	InvalView();
}


void TMeshView::SetShowMeshPolys( bool newMode )
{
	if (newMode == showMeshPolys) return;
	showMeshPolys = newMode;
	InvalView();
}


void TMeshView::SetShowTerrainType( bool newMode )
{
	if (newMode == showTerrainType) return;
	showTerrainType = newMode;
	InvalView();
}


void TMeshView::SetGridVisibility( int which_height, bool shown )
{
	if (meshHeightShown[which_height] == shown) return;
	meshHeightShown[which_height] = shown;
	InvalView();
}


void TMeshView::SetMeshHeightChangeMask( int which_height, bool modifiable )
{
	if (meshHeightMask[which_height] == modifiable) return;
	meshHeightMask[which_height] = modifiable;
	InvalView();
}


void TMeshView::SetClickableMeshHeight( int which_height )
{
	if (targetHeightKind == which_height) return;
	targetHeightKind = which_height;
	InvalView();
}


void TMeshView::SetMarkerViewMode( int newMode )
{
	if (newMode == markerViewMode) return;
	switch (newMode) {
		case KMenuMeshMarkersOff:
			break;
		case KMenuMeshMarkersOnly:
			break;
		case KMenuMeshBoundingBoxes:
			break;
		case KMenuMeshSimpleModels:
			ResetAllSkelmodels();
			break;
		case KMenuMeshComplexModels:
			ResetAllSkelmodels();
			break;
		default:
			return;
	}
	markerViewMode = newMode;
	InvalView();
}


vec3 TMeshView::point_to_vector( XPoint pt )
{
	if (!form)
	{
		return vec3( 0, 1, 0 );
	}
	XRect bounds = GetContentRect();
	float width = bounds.right - bounds.left;
	float height = bounds.bottom - bounds.top;
	float off_x = 1.0f - 2.0f * (float)(pt.h - bounds.left) / width;
	float off_y = 1.0f - 2.0f * (float)(pt.v - bounds.top) / height;
	
	float max_fov = 50.0f;
	float tan_half_max_fov = tanf( 0.5f * max_fov );
	float vx, vy;
	vx = vy = tan_half_max_fov;
	
	if (width < height)
	{
		vy *= height / width;
	}
	else
	{
		vx *= width / height;
	}
	
	vec3 out = form->view_forward + off_x * vx * form->view_right + off_y * vy * form->view_up;
	return normalized(out);
}


float TMeshView::line_intersects_cell( vec3 origin, vec3 direction, int cell_x, int cell_y, float slop, int which_height )
{
	if (!form) return -1.0f;
	mesh_cell *c00 = form->get_cell( cell_x, cell_y );
	mesh_cell *c01 = form->get_cell( cell_x, cell_y+1 );
	mesh_cell *c10 = form->get_cell( cell_x+1, cell_y );
	mesh_cell *c11 = form->get_cell( cell_x+1, cell_y+1 );
	if (!c00 | !c01 || !c10 || !c11) return -1.0f;
	vec3 p00 = vec3( cell_x, cell_y, cell_float_height( c00, which_height ) );
	vec3 p01 = vec3( cell_x, cell_y+1, cell_float_height( c01, which_height ) );
	vec3 p10 = vec3( cell_x+1, cell_y, cell_float_height( c10, which_height ) );
	vec3 p11 = vec3( cell_x+1, cell_y+1, cell_float_height( c11, which_height ) );
	if (which_height == _cell_media_height)
	{
		if (!(c00->flags & _mesh_vertex_is_media_flag)) p00.z = WORLD_TO_FLOAT(targetMediaHeight);
		if (!(c01->flags & _mesh_vertex_is_media_flag)) p01.z = WORLD_TO_FLOAT(targetMediaHeight);
		if (!(c10->flags & _mesh_vertex_is_media_flag)) p10.z = WORLD_TO_FLOAT(targetMediaHeight);
		if (!(c11->flags & _mesh_vertex_is_media_flag)) p11.z = WORLD_TO_FLOAT(targetMediaHeight);
	}
	vec3 plane_dir;
	float plane_const;
	float d0, d1;
	vec3 intersect;
	if (!((cell_x ^ cell_y)&1))
	{
		plane_dir = cross( p00 - p10, p11 - p10 );
		plane_const = -dot( p10, plane_dir );
		d0 = line_intersects_plane( origin, direction, plane_dir, plane_const );
		intersect = origin + d0 * direction;
		if (intersect.x < cell_x - slop || intersect.x > cell_x + 1.0f + slop) d0 = -1.0f;
		if (intersect.y < cell_y - slop || intersect.y > cell_y + 1.0f + slop) d0 = -1.0f;
		if (intersect.x - intersect.y < cell_x - cell_y + slop) d0 = -1.0f;
		
		plane_dir = cross( p00 - p01, p11 - p01 );
		plane_const = -dot( p01, plane_dir );
		d1 = line_intersects_plane( origin, direction, plane_dir, plane_const );
		intersect = origin + d1 * direction;
		if (intersect.x < cell_x - slop || intersect.x > cell_x + 1.0f + slop) d1 = -1.0f;
		if (intersect.y < cell_y - slop || intersect.y > cell_y + 1.0f + slop) d1 = -1.0f;
		if (intersect.x - intersect.y > cell_x - cell_y - slop) d1 = -1.0f;
	}
	else
	{
		plane_dir = cross( p10 - p00, p01 - p00 );
		plane_const = -dot( p00, plane_dir );
		d0 = line_intersects_plane( origin, direction, plane_dir, plane_const );
		intersect = origin + d0 * direction;
		if (intersect.x < cell_x - slop || intersect.x > cell_x + 1.0f + slop) d0 = -1.0f;
		if (intersect.y < cell_y - slop || intersect.y > cell_y + 1.0f + slop) d0 = -1.0f;
		if (intersect.x + intersect.y > cell_x + cell_y + 1.0 + slop) d0 = -1.0f;
		
		plane_dir = cross( p10 - p11, p01 - p11 );
		plane_const = -dot( p11, plane_dir );
		d1 = line_intersects_plane( origin, direction, plane_dir, plane_const );
		intersect = origin + d1 * direction;
		if (intersect.x < cell_x - slop || intersect.x > cell_x + 1.0f + slop) d1 = -1.0f;
		if (intersect.y < cell_y - slop || intersect.y > cell_y + 1.0f + slop) d1 = -1.0f;
		if (intersect.x + intersect.y < cell_x + cell_y + 1.0 - slop) d1 = -1.0f;
	}
	
	if (d0 < 0.0f) return d1;
	if (d1 < 0.0f) return d0;
	if (d0 < d1) return d0;
	return d1;
}


bool TMeshView::line_intersects_mesh( vec3 origin, vec3 direction, int *out_vertex_x, int *out_vertex_y, vec3 *out_point, bool pick_vertices, bool transparent_mesh, int which_height )
{
	if (!form) return false;
	
	XRect bounds = GetContentRect();
	int width = bounds.right - bounds.left;
	int height = bounds.bottom - bounds.top;
	float viewscale = (width > height ? width : height);
	
	int cell_x = origin.x;
	int cell_y = origin.y;
	for (;;)
	{
		mesh_cell *cell = form->get_cell( cell_x, cell_y );
		if (cell)
		{
			float slop = pick_vertices ? 1.5 * dot( vec3(cell_x + 0.5, cell_y + 0.5, cell_float_height( cell, which_height )) - form->view_origin, form->view_forward ) / viewscale : 0.001f;
			float intersect_dist = line_intersects_cell( origin, direction, cell_x, cell_y, slop, which_height );
			if (intersect_dist > 0.0f)
			{
				vec3 intersect_point = origin + intersect_dist * direction;
				if (out_point) *out_point = intersect_point;
				if (pick_vertices)
				{
					if ((fabs(intersect_point.x - cell_x) < slop || fabs(intersect_point.x - 1.0f - cell_x) < slop) &&
						(fabs(intersect_point.y - cell_y) < slop || fabs(intersect_point.y - 1.0f - cell_y) < slop))
					{
						if (out_vertex_x) *out_vertex_x = (int)(intersect_point.x + 0.5f);
						if (out_vertex_y) *out_vertex_y = (int)(intersect_point.y + 0.5f);
						return true;
					}
					if (!transparent_mesh) return false;
				}
				else
				{
					if (out_vertex_x) *out_vertex_x = (int)floor(intersect_point.x);
					if (out_vertex_y) *out_vertex_y = (int)floor(intersect_point.y);
					return true;
				}
			}
		}
		float max_dist = 3.0f * form->view_dist;
		float dist_x, dist_y;
		dist_x = dist_y = max_dist;
		if (direction.x < 0.0f)
		{
			dist_x = (cell_x - origin.x) / direction.x;
		}
		else if (direction.x > 0.0f)
		{
			dist_x = (cell_x + 1 - origin.x) / direction.x;
		}
		
		if (direction.y < 0.0f)
		{
			dist_y = (cell_y - origin.y) / direction.y;
		}
		else if (direction.y > 0.0f)
		{
			dist_y = (cell_y + 1 - origin.y) / direction.y;
		}
		
		if (dist_y >= max_dist && dist_x >= max_dist) return false;
		
		if (dist_x < dist_y)
		{
			if (direction.x < 0.0f) --cell_x; else ++cell_x;
		}
		else
		{
			if (direction.y < 0.0f) --cell_y; else ++cell_y;
		}
	}
}


float TMeshView::get_mesh_height( float x, float y, int which_height )
{
	vec3 up( 0.0f, 0.0f, 1.0f );
	vec3 base( x, y, -200.0f );
	if (!line_intersects_mesh( base, up, 0, 0, &base, false, false, which_height )) return 0.0f;
	// do it again to get good precision
	base.z -= 1.0f;
	if (!line_intersects_mesh( base, up, 0, 0, &base, false, false, which_height )) return base.z + 1.0f;
	return base.z;
}


bool TMeshView::line_intersects_marker( vec3 v_origin, vec3 mouse_vec, int *out_marker, int *out_axis, vec3 *out_point )
{
	int clicked_marker = -1;
	int clicked_part = -1;
	float clicked_dist = 10000000.0f;
	XRect bounds = GetContentRect();
	int width = bounds.right - bounds.left;
	int height = bounds.bottom - bounds.top;
	float viewscale = (width > height ? width : height);
	for (int marker_index = 0; marker_index < marker_cache.Length(); ++marker_index)
	{
		cached_marker_data &c = marker_cache[marker_index];
		vec3 &m_origin = *(vec3 *)(c.endpoints[0][0]);
		vec3 m_dist = v_origin - m_origin;
		vec3 m_to_line_offset = m_dist - (dot(mouse_vec, m_dist) / dot(mouse_vec,mouse_vec)) * mouse_vec;
		float m_to_line_dist_squared = length_squared( m_to_line_offset );
		if (m_to_line_dist_squared > AXIS_LENGTH * AXIS_LENGTH) continue;
		float slop = -1.5 * dot( m_dist, form->view_forward ) / viewscale;
		if (m_to_line_dist_squared < slop * slop)
		{
			float test_dist = -dot( m_dist, form->view_forward );
			if (test_dist < clicked_dist)
			{
				clicked_marker = marker_index;
				clicked_part = -1;
				clicked_dist = test_dist;
			}
		}
		else
		{
			for (int test_axis = 0; test_axis < 3; ++test_axis)
			{
				vec3 &axis_end = *(vec3 *)(c.endpoints[test_axis][1]);
				vec3 axis_dir = axis_end - m_origin;
				float axis_dist = distance_along_line( m_origin, axis_dir, v_origin, mouse_vec );
				if (axis_dist < 0.0f || axis_dist > 1.0f + slop / AXIS_LENGTH) continue;
				vec3 axis_pt = m_origin + axis_dist * axis_dir;
				vec3 a_dist = v_origin - axis_pt;
				vec3 a_to_line_offset = a_dist - (dot(mouse_vec, a_dist) / dot(mouse_vec,mouse_vec)) * mouse_vec;
				float a_to_line_dist_squared = length_squared( a_to_line_offset );
				if (a_to_line_dist_squared > slop * slop) continue;
				float test_dist = dot( axis_pt - v_origin, form->view_forward );
				if (test_dist < clicked_dist)
				{
					clicked_marker = marker_index;
					clicked_part = test_axis;
					clicked_dist = test_dist;
				}
			}
		}
	}
	
	if (out_marker) *out_marker = clicked_marker;
	if (out_axis) *out_axis = clicked_part;
	if (out_point) *out_point = v_origin + clicked_dist * mouse_vec;
	return clicked_marker >= 0;
}


float TMeshView::get_soft_brush_level( float distance )
{
	if (distance < 0.0f) return 1.0f;
	if (!(distance < softBrushRadius)) return 0.0f;
	distance /= softBrushRadius;
	
	float mc = 3.1415926535 * (0.005 + 0.95f * softBrushSoftness);
	float ch = cos( mc * distance );
	float oh = cos( mc );
	float round_height = 1.0f - (1.0f - ch) / (1.0f - oh);
	
	float flat_height = 1.0f - softBrushSoftness * distance;
	
	return flat_height + softBrushRoundness * (round_height - flat_height);
}

