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

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

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

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

#ifndef PandaWave_XWINDOW
#include <XWindow.h>
#endif

#ifndef PandaWave_XDATAUTIL
#include "XDataUtil.h"
#endif

#include <XAppMenu.h>

#include <XPopup.h>
#include <XList.h>
#include <XDialog.h>
#include "formatters.h"
#include "tagpick.h"
#include "revengeapp.h"
#include <ctype.h>
#include "TBitmapView.h"


byte_swap_code long_byte_swap_data[]= {_begin_bs_array, 1, _4byte, _end_bs_array};

static const char *markerTypeNames[] =
{ "Observer", "Scenery", "<unused1>", "Monster", "<unused2>", "Sound Source", "<myth2 model>",
	"<unused3>", "<unused4>", "Projectile", "Local Projectile Group", "<myth2 model animation>" };

static const int markerTypeToPopupIndex[] = 
{ 0, 1, -1, 2, -1, 3, -1, -1, -1, 4, 5, -1 };

static const int markerPopupIndexToType[] =
{ _marker_team, _marker_scenery, _marker_monster, _marker_sound_source, _marker_projectile, _marker_local_projectile_group };


// ==============================  EDIT WINDOW  ========================================
void edit_mesh( tag_entry* e )
{
	TMeshForm* form = new TMeshForm( e );
	RegisterView(TMeshView);
	form->AttachWindow( KWindowMeshEditor );
}

void dependency_mesh( tag_entry *e )
{
	int i, j;
	
	// first the header
	mesh_definition def;
	read_mesh_definition( &def, e );
	
	add_tag_dependency( 'txst', def.landscape_texturestack_tag );
	add_tag_dependency( 'txst', def.detail_stack_tag );
	add_tag_dependency( 'grsp', def.ground_sprites_tag );
	add_tag_dependency( 'txst', def.collapsed_detail_stack_tag );
	add_tag_dependency( 'medi', def.media_tag );
	add_tag_dependency( 'glli', def.mesh_lighting_tag );
	add_tag_dependency( 'txst', def.environment_texture_tag );
	add_tag_dependency( 'part', def.particle_system_tag );
	add_tag_dependency( 'amso', def.global_ambient_sound_tag );
	add_tag_dependency( 'stli', def.map_description_string_list_tag );
	add_tag_dependency( 'wind', def.wind_tag );
	add_tag_dependency( 'soun', def.narration_sound_tag );
	add_tag_dependency( 'conn', def.connector_tag );
	add_tag_dependency( 'stli', def.hints_string_list_tag );

	add_tag_dependency( '.256', def.pregame_collection_tag );
	add_tag_dependency( '.256', def.overhead_map_collection_tag );
	add_tag_dependency( '.256', def.postgame_collection_tag );

	add_tag_dependency( 'text', def.storyline_string_tags[_storyline_pregame] );
	add_tag_dependency( 'stli', def.picture_caption_string_list_tag );
	
	add_tag_dependency( 'jpeg', def.postgame_pics[0] );
	add_tag_dependency( 'jpeg', def.postgame_pics[1] );
	add_tag_dependency( 'text', def.postgame_text[0] );
	add_tag_dependency( 'text', def.postgame_text[1] );
	add_tag_dependency( 'quik', def.postgame_mp3[0] );
	add_tag_dependency( 'quik', def.postgame_mp3[1] );
		
	add_tag_dependency( '.256', def.level_picture_tag );
	
	// now the markers
	const byte *raw = (const byte *)get_entry_data( e );
	for (i = 0; i < def.marker_palette_entries; ++i)
	{
		marker_palette_entry m;
		byte_swap_move_be( "marker palette", &m, raw + def.data_offset + def.marker_palette_offset + i * sizeof(marker_palette_entry), sizeof(marker_palette_entry), 1, marker_palette_data_bs_data );
		switch (m.type)
		{
			case _marker_scenery:
				add_tag_dependency( 'scen', m.tag );
				break;
			case _marker_monster:
				add_tag_dependency( 'unit', m.tag );
				break;
			case _marker_sound_source:
				add_tag_dependency( 'amso', m.tag );
				break;
			case _marker_projectile:
				add_tag_dependency( 'proj', m.tag );
				break;
			case _marker_local_projectile_group:
				add_tag_dependency( 'lpgr', m.tag );
				break;
		}
	}
	
	// now the script
	const byte *parameter_start = raw + def.data_offset + def.map_actions_offset + def.map_action_count * sizeof(map_action);
	const byte *parameters_end = raw + def.data_offset + def.map_actions_offset + def.map_action_buffer_size;
	while (parameter_start < parameters_end)
	{
		map_action_parameter_header ph;
		byte_swap_move_be( "map action parameter header", &ph, parameter_start,
							sizeof(ph), 1, map_action_parameter_header_byte_swap_data );
		parameter_start += sizeof(ph);
		file_tag type;
		int size;
		
		switch (ph.type)
		{
			case _parameter_flag:
				type = 0; size = 1; break;
				
			case _parameter_character:
				type = 0; size = 1; break;
				
			case _parameter_monster_identifier:
				type = 0; size = 2; break;
				
			case _parameter_action_identifier:
				type = 0; size = 2; break;
				
			case _parameter_angle:
				type = 0; size = 2; break;
				
			case _parameter_integer:
				type = 0; size = 4; break;
				
			case _parameter_world_distance:
				type = 0; size = 4; break;
				
			case _parameter_field_name:
				type = 0; size = 4; break;
				
			case _parameter_fixed:
				type = 0; size = 4; break;
				
			case _parameter_projectile_group_tag:
				type = 'prgr'; size = 4; break;
				
			case _parameter_string_list_tag:
				type = 'stli'; size = 4; break;
				
			case _parameter_sound_tag:
				type = 'soun'; size = 4; break;
				
			case _parameter_projectile_tag:
				type = 'proj'; size = 4; break;
				
			case _parameter_world_point2d:
				type = 0; size = 8; break;
				
			case _parameter_world_rectangle2d:
				type = 0; size = 16; break;
				
			case _parameter_object_identifier:
				type = 0; size = 2; break;
				
			case _parameter_model_identifier:
				type = 0; size = 2; break;
				
			case _parameter_sound_source_identifier:
				type = 0; size = 2; break;
				
			case _parameter_world_point3d:
				type = 0; size = 12; break;
				
			case _parameter_local_projectile_group_identifier:
				type = 0; size = 2; break;
				
			case _parameter_model_animation_identifier:
				type = 0; size = 2; break;
				
			case _parameter_scenery_identifier:
				type = 0; size = 2; break;
				
			case _parameter_scenery_tag:
				type = 'scen'; size = 4; break;
				
			default:
				type = 0; size = parameters_end - parameter_start; ph.count = 1; break;
		}
		
		if (type)
		{
			for (j = 0; j < ph.count; ++j)
			{
				file_tag id;
				byte_swap_move_be( "map action parameter element", &id, parameter_start,
									sizeof(id), 1, long_byte_swap_data );
				add_tag_dependency( type, id );
				parameter_start += 4;
			}
		}
		else
		{
			// it seems as parameters are dword aligned
			parameter_start += (((size * ph.count) + 3) & 0xfffffffc);
		}
	}
	
	// do this LAST to optimize the file layout
	for (i = 0; i < NUMBER_OF_NEXT_MESH_TAGS; ++i)
	{
		add_tag_dependency( 'mesh', def.next_mesh_tags[i] );
	}
}


// ==============================  DISPATCH  ========================================
BEGINDISPATCH(TMeshForm,TTagEditForm)
	DISPATCHTABLE(KXGControlClick,TMeshForm::DoControlClick)
	DISPATCHTABLE(KXGKeyClick,TMeshForm::DoKeyClick)
	DISPATCHTABLE(KXGKeyEvent,TMeshForm::DoKeyEvent)
	DISPATCHTABLE(KXGGetMenuStatus,TMeshForm::DoGetMenuStatus)
	DISPATCHTABLE(KXGDoMenuCommand,TMeshForm::DoMenuCommand)
	DISPATCHTABLE(KXGPopValue,TMeshForm::DoPopValue)
	DISPATCHTABLE(KXGListValue,TMeshForm::DoListValue)
	DISPATCHTABLE(KXGSliderValue,TMeshForm::DoSliderValue)
ENDDISPATCH


TMeshForm::TMeshForm( struct tag_entry *e ) : TTagEditForm( e )
{
	fModified = false;
	cells = 0;
	
	media_coverage_region = 0;
	mesh_LOD_data = 0;
	connector_data = 0;
	map_action_data = 0;
}

TMeshForm::~TMeshForm()
{
	free_all();
}


// ==============================  INPUT HANDLING  ========================================
long TMeshForm::DoControlClick( long arg, void * /* ptr */ )
{
	if (!mesh_view) return -1;
	switch (arg)
	{
		case 4: // new marker palette entry
			if (selectedPaletteType >= 0 && selectedPaletteType < NUMBER_OF_MARKER_TYPES)
			{
				marker_palette_dlog_info info;
				if (selectedPaletteEntry[selectedPaletteType] >= 0 && selectedPaletteEntry[selectedPaletteType] < marker_palette.Length())
				{
					info.m = marker_palette[selectedPaletteEntry[selectedPaletteType]];
				}
				else
				{
					memset( &info.m, 0, sizeof(info.m) );
					
					info.m.type = selectedPaletteType;
					info.m.flags = 0;
					info.m.tag = -1;
					info.m.team_index = -1;
					info.m.extra_flags = (1 << 14) - 1;
					info.m.count = 0;
					info.m.tag_index = -1;
					if (selectedPaletteType == _marker_monster)
					{
						info.m.flags |= _marker_palette_may_be_traded_flag|_marker_palette_may_use_veterans_flag;
						info.m.team_index = 0;
					}
				}
				info.f = get_entry_file( fEntry );
				if (XGDialog::ModalDialog( 168, MarkerPaletteDlogProc, &info ))
				{
					int palette_index = marker_palette.Length();
					marker_palette.Insert( palette_index, info.m );
					selectedPaletteEntry[selectedPaletteType] = palette_index;
					
					RefreshMarkerPalette();
					MarkModified();
				}
			}
			return 0;
		case 5: // edit marker palette entry
			if (selectedPaletteType >= 0 && selectedPaletteType < NUMBER_OF_MARKER_TYPES)
			{
				if (selectedPaletteEntry[selectedPaletteType] >= 0 && selectedPaletteEntry[selectedPaletteType] < marker_palette.Length())
				{
					marker_palette_dlog_info info;
					info.m = marker_palette[selectedPaletteEntry[selectedPaletteType]];
					info.f = get_entry_file( fEntry );
					if (XGDialog::ModalDialog( 168, MarkerPaletteDlogProc, &info ))
					{
						marker_palette[selectedPaletteEntry[selectedPaletteType]] = info.m;
						RefreshMarkerPalette();
						mesh_view->ResetMarkers();
						mesh_view->InvalView();
						
						MarkModified();
					}
				}
			}
			return 0;
		case 6: // delete marker palette entry
			if (selectedPaletteType >= 0 && selectedPaletteType < NUMBER_OF_MARKER_TYPES)
			{
				int palette_index = selectedPaletteEntry[selectedPaletteType];
				if (palette_index >= 0 && palette_index < marker_palette.Length())
				{
					mesh_view->ClearSelection();
					
					int i = 0;
					while (i < markers.Length())
					{
						if (markers[i].palette_index == palette_index)
						{
							markers.Delete(i);
						}
						else 
						{
							if (markers[i].palette_index > palette_index)
							{
								markers[i].palette_index -= 1;
							}
							++i;
						}
					}
					
					selectedPaletteEntry[selectedPaletteType] = -1;
					for (i = 0; i < NUMBER_OF_MARKER_TYPES; ++i)
					{
						if (selectedPaletteEntry[i] > palette_index)
						{
							selectedPaletteEntry[i] -= 1;
						}
					}
					marker_palette.Delete(palette_index);
					RefreshMarkerPalette();
					mesh_view->ResetMarkers();
					mesh_view->InvalView();
					
					MarkModified();
				}
			}
			return 0;
		case 8: // select marker palette entry
			if (selectedPaletteType >= 0 && selectedPaletteType < NUMBER_OF_MARKER_TYPES)
			{
				int palette_index = selectedPaletteEntry[selectedPaletteType];
				if (palette_index >= 0 && palette_index < marker_palette.Length())
				{
					int i = 0;
					mesh_view->ClearSelection();
					while (i < markers.Length())
					{
						if (markers[i].palette_index == palette_index)
						{
							mesh_view->markerSelect += &(markers[i]);
						}
						++i;
					}
					mesh_view->InvalView();
				}
			}
			return 0;
		
		case _mesh_tool_band_grid:
		case _mesh_tool_band_markers:
		case _mesh_tool_water:
		case _mesh_tool_terrain:
		case _mesh_tool_pull_grid:
		case _mesh_tool_smooth_grid:
			SetCurrentTool( arg );
			mesh_view->InvalView();
			return 0;
		
		case 150: return DoMenuCommand( KMenuMeshClickRenderGrid, NULL );
		case 151: return DoMenuCommand( KMenuMeshClickPhysicsGrid, NULL );
		case 152: return DoMenuCommand( KMenuMeshClickMediaGrid, NULL );
		case 153: return DoMenuCommand( KMenuMeshClickNone, NULL );
		
		case 160: return DoMenuCommand( KMenuMeshModifyRenderGrid, NULL );
		case 161: return DoMenuCommand( KMenuMeshModifyPhysicsGrid, NULL );
		case 162: return DoMenuCommand( KMenuMeshModifyMediaGrid, NULL );
		case 163: return DoMenuCommand( KMenuMeshModifyMarkers, NULL );
		
		case 170: return DoMenuCommand( KMenuMeshShowRenderGrid, NULL );
		case 171: return DoMenuCommand( KMenuMeshShowPhysicsGrid, NULL );
		case 172: return DoMenuCommand( KMenuMeshShowMediaGrid, NULL );
		case 173: return DoMenuCommand( KMenuMeshShowTerrainType, NULL );
	}
	
	if (arg >= 500 && arg < 516)
	{
		SetTargetTerrainType( arg - 500 );
		return 0;
	}
	
	return -1;
}

		
long TMeshForm::DoSliderValue( long arg, void * /* ptr */ )
{
	if (!mesh_view) return -1;
	switch (arg)
	{
		case 301:
		case 302:
		case 303:
			UpdateSoftBrush();
			return 0;
	}
	
	return -1;
}


long TMeshForm::DoPopValue( long /* arg */, void * /* ptr */ )
{
	int value = markerPopupIndexToType[markerPaletteType->GetValue()];
	if (value != selectedPaletteType)
	{
		selectedPaletteType = value;
		RefreshMarkerPalette();
	}
	return 0;
}

		
		
long TMeshForm::DoListValue( long /* arg */, void * /* ptr */ )
{
	int value = markerPalette->GetValue();
	int index = 0;
	selectedPaletteEntry[selectedPaletteType] = -1;
	for (int i = 0; i < marker_palette.Length(); ++i)
	{
		if (marker_palette[i].type == selectedPaletteType)
		{
			if (index == value)
			{
				selectedPaletteEntry[selectedPaletteType] = i;
				break;
			}
			++index;
		}
	}
	return 0;
}


long TMeshForm::DoKeyClick( long arg, void * /* ptr */ )
{
	switch (arg)
	{
		case 20: // target height
			{
				mesh_view->targetMediaHeight = GetWorldDistance( arg );
			}
			break;
	}
	int value = markerPalette->GetValue();
	int index = 0;
	selectedPaletteEntry[selectedPaletteType] = -1;
	for (int i = 0; i < marker_palette.Length(); ++i)
	{
		if (marker_palette[i].type == selectedPaletteType)
		{
			if (index == value)
			{
				selectedPaletteEntry[selectedPaletteType] = i;
				break;
			}
			++index;
		}
	}
	return -1;
}





// ========================================  SAVE TO TAG  ==================================================
void TMeshForm::CommitToTag()
{
	int i, j;
	
	mesh_definition h = header;
	
	h.data = 0;
	h.data_offset = sizeof(h);
	h.data_size = 0;
	
	h.mesh = 0;
	h.mesh_offset = h.data_size;
	h.mesh_size = sizeof(mesh_cell) * width * height;
	h.data_size += h.mesh_size;
	
	h.marker_palette = 0;
	h.marker_palette_entries = marker_palette.Length();
	h.marker_palette_offset = h.data_size;
	h.marker_palette_size = sizeof(marker_palette_entry) * h.marker_palette_entries;
	h.data_size += h.marker_palette_size;
	
	h.markers = 0;
	h.marker_count = markers.Length();
	h.markers_offset = h.data_size;
	h.markers_size = sizeof(marker_data) * h.marker_count;
	h.data_size += h.markers_size;
	
	h.media_coverage_region = 0;
	h.media_coverage_region_offset = h.data_size;
	h.media_coverage_region_size = media_coverage_region_size;
	h.data_size += h.media_coverage_region_size;
	
	h.mesh_LOD_data = 0;
	h.mesh_LOD_data_offset = h.data_size;
	h.mesh_LOD_data_size = mesh_LOD_data_size;
	h.data_size += h.mesh_LOD_data_size;
	
	h.connectors = 0;
	h.connectors_offset = h.data_size;
	h.connectors_size = connector_data_size;
	h.data_size += h.connectors_size;
	
	h.map_actions_offset = h.data_size;
	h.map_action_buffer_size = map_action_data_size;
	h.data_size += h.map_action_buffer_size;
	
	
	byte *raw = (byte *)malloc( h.data_size + h.data_offset );
	
	byte_swap_move_be( "mesh", raw, &h, SIZEOF_STRUCT_MESH_DEFINITION, 1, mesh_definition_byte_swapping_data );
	
	for (i = 0; i < width * height; ++i)
	{
		cells[i].render_height -= cells[i].physical_height + 1;
	}
	byte_swap_move_be( "mesh cells",
						raw + h.data_offset + h.mesh_offset,
						cells,
						sizeof(mesh_cell),
						width * height,
						mesh_cell_bs_data );
	for (i = 0; i < width * height; ++i)
	{
		cells[i].render_height += cells[i].physical_height + 1;
	}
	
	for (i = 0; i < marker_palette.Length(); ++i)
	{
		marker_palette_entry mp = marker_palette[i];
		mp.render_chain = 0;
		mp.count = 0;
		mp.tag_index = -1;
		for (j = 0; j < markers.Length(); ++j)
		{
			if (markers[j].palette_index == i)
			{
				mp.count += 1;
			}
		}
		
		byte_swap_move_be( "marker palette",
							raw + h.data_offset + h.marker_palette_offset + i * sizeof(marker_palette_entry),
							&mp,
							sizeof(mp),
							1,
							marker_palette_data_bs_data );
	}
	
	for (i = 0; i < markers.Length(); ++i)
	{
		marker_data m = markers[i];
		m.render_chain = 0;
		int palette_index = m.palette_index;
		m.palette_index = 0;
		for (j = 0; j < palette_index; ++j)
		{
			if (marker_palette[j].type == m.type)
			{
				m.palette_index += 1;
			}
		}
		
		byte_swap_move_be( "marker",
							raw + h.data_offset + h.markers_offset + i * sizeof(marker_data),
							&m,
							sizeof(m),
							1,
							marker_data_bs_data );
	}
	
	memcpy( raw + h.data_offset + h.media_coverage_region_offset, media_coverage_region, media_coverage_region_size );
	memcpy( raw + h.data_offset + h.mesh_LOD_data_offset, mesh_LOD_data, mesh_LOD_data_size );
	memcpy( raw + h.data_offset + h.connectors_offset, connector_data, connector_data_size );
	memcpy( raw + h.data_offset + h.map_actions_offset, map_action_data, map_action_data_size );
	
	set_entry_data( fEntry, raw, h.data_offset + h.data_size, true );
	
	fModified = false;
}


// ========================================  RELOAD TAG  ==================================================
void TMeshForm::ReloadTag()
{
	free_all();
	int i, j;
	
	const byte *raw = (const byte *)get_entry_data( fEntry );
	byte_swap_move_be( "mesh header", &header, raw + 0, sizeof(header), 1, mesh_definition_byte_swapping_data );
	width = header.submesh_width * SUBMESH_CELL_WIDTH;
	height = header.submesh_height * SUBMESH_CELL_WIDTH;
	cells = new mesh_cell[ width * height ];
	byte_swap_move_be( "mesh cells", cells, raw + header.data_offset + header.mesh_offset, sizeof(mesh_cell), width * height, mesh_cell_bs_data );
	for (i = 0; i < width * height; ++i)
	{
		cells[i].render_height += cells[i].physical_height + 1;
	}
	for (i = 0; i < height; ++i)
	{
		for (j = 0; j < width; ++j)
		{
			struct mesh_cell *c = get_cell( j, i );
			if (c && AT_LEAST_PARTIALLY_MEDIA(c))
			{
				c->flags |= _mesh_cell_triangle0_is_media_flag|_mesh_cell_triangle1_is_media_flag;
			}
			fix_vertex_media_flag( j, i );
		}
	}
	
	mesh_view = dynamic_cast<TMeshView *>(GetWindow()->FindViewByID(1));
	
	for (i = 0; i < header.marker_palette_entries; ++i)
	{
		marker_palette_entry palette_entry;
		byte_swap_move_be( "marker palette", &palette_entry, raw + header.data_offset + header.marker_palette_offset + i * sizeof(marker_palette_entry), sizeof(marker_palette_entry), 1, marker_palette_data_bs_data );
		
		marker_palette += palette_entry;
	}
	
	next_identifier = 10000;
	
	for (i = 0; i < header.marker_count; ++i)
	{
		marker_data marker;
		byte_swap_move_be( "marker", &marker, raw + header.data_offset + header.markers_offset + i * sizeof(marker_data), sizeof(marker_data), 1, marker_data_bs_data );
		int count = 0;
		for (j = 0; j < header.marker_palette_entries; ++j)
		{
			if (marker_palette[j].type == marker.type)
			{
				if (count == marker.palette_index)
				{
					marker.palette_index = j;
					break;
				}
				else
				{
					++count;
				}
			}
		}
		if (marker.identifier >= next_identifier)
		{
			next_identifier = marker.identifier + 1;
		}
		markers += marker;
	}
	
	media_coverage_region_size = header.media_coverage_region_size;
	media_coverage_region = malloc( media_coverage_region_size );
	memcpy( media_coverage_region,
			raw + header.data_offset + header.media_coverage_region_offset,
			media_coverage_region_size );
	
	mesh_LOD_data_size = header.mesh_LOD_data_size;
	mesh_LOD_data = malloc( mesh_LOD_data_size );
	memcpy( mesh_LOD_data,
			raw + header.data_offset + header.mesh_LOD_data_offset,
			mesh_LOD_data_size );
	
	connector_data_size = header.connectors_size;
	connector_data = malloc( connector_data_size );
	memcpy( connector_data,
			raw + header.data_offset + header.connectors_offset,
			connector_data_size );
	
	map_action_data_size = header.map_action_buffer_size;
	map_action_data = malloc( map_action_data_size );
	memcpy( map_action_data,
			raw + header.data_offset + header.map_actions_offset,
			map_action_data_size );
	
	
	mesh_view->ResetMesh( this );
	
	view_focus.x = 0.5 * width;
	view_focus.y = 0.5 * height;
	view_pitch = 30.0f;
	view_yaw = -90.0f;
	view_dist = 30.0f;
	UpdateViewData();
	
	selectedPaletteType = _marker_scenery;
	for (i = 0; i < NUMBER_OF_MARKER_TYPES; ++i)
	{
		selectedPaletteEntry[i] = -1;
	}
	
	markerPaletteType = dynamic_cast<XGPopControl *>(GetWindow()->FindViewByID(3));
	markerPalette = dynamic_cast<XGListControl *>(GetWindow()->FindViewByID(7));
	markerPaletteType->DeleteAll();
	for (i = 0; i < NUMBER_OF_MARKER_TYPES; ++i)
	{
		if (markerTypeToPopupIndex[i] != -1)
		{
			markerPaletteType->Insert( markerTypeToPopupIndex[i], markerTypeNames[i] );
		}
	}
	markerPaletteType->SetValue( markerTypeToPopupIndex[selectedPaletteType] );
	RefreshMarkerPalette();
	
	SetCurrentTool( _mesh_tool_first_index );
	
	for (i = 0; i < 16; ++i)
	{
		rgb_color c;
		c.red =   0x101 * TMeshView::terrain_type_colors[i][0];
		c.green = 0x101 * TMeshView::terrain_type_colors[i][1];
		c.blue =  0x101 * TMeshView::terrain_type_colors[i][2];
		SetColor( 600 + i, c );
	}
	
	SetTargetTerrainType( _terrain_grass );
	
	mesh_view->softBrushRadius = 2.5;
	SetInt( 301, log10(mesh_view->softBrushRadius) * 1000 );
	mesh_view->softBrushRoundness = 1.0f;
	SetInt( 302, mesh_view->softBrushRoundness * 1000 );
	mesh_view->softBrushSoftness = 1.0f;
	SetInt( 303, mesh_view->softBrushSoftness * 1000 );
	UpdateSoftBrush();
	
	fModified = false;
}


void TMeshForm::UpdateSoftBrush()
{
	mesh_view = dynamic_cast<TMeshView *>(GetWindow()->FindViewByID(1));
	if (mesh_view)
	{
		TBitmapView *bmp = dynamic_cast<TBitmapView *>(GetWindow()->FindViewByID(300));
		if (bmp)
		{
			mesh_view->softBrushRadius = pow( 10.0, GetInt( 301 ) / 1000.0 );
			mesh_view->softBrushRoundness = GetInt( 302 ) / 1000.0;
			mesh_view->softBrushSoftness = GetInt( 303 ) / 1000.0;
			
			XRect r = bmp->GetContentRect();
			int w = r.right - r.left;
			int h = r.bottom - r.top;
			byte *buf = (byte *)malloc( w * h );
			if (buf)
			{
				int y, x;
				
				for (y = 0; y < h; ++y)
				{
					for (x = 0; x < w; ++x)
					{
						buf[y*w+x] = (((y^x) & 8) ? 0xff : 0xcc);
					}
				}
				
				float sx = mesh_view->softBrushRadius / (0.5f * w);
				float sy = 0.9 * h;
				
				for (x = 0; x < w; ++x)
				{
					float d = fabs( (x - (w>>1)) * sx );
					float level = mesh_view->get_soft_brush_level( d );
					buf[ (int)(h - 1 - sy * level) * w + x ] = 0;
				}
				
				bmp->SetBitmapAlpha( buf, w, h );
				bmp->InvalView();
			}
		}
	}
}


void TMeshForm::SetTargetTerrainType( int type )
{
	if (type >= 0 && type < 16)
	{
		targetTerrainType = type;
		for (int i = 0; i < 16; ++i)
		{
			SetFlag( 500 + i, i == type );
		}
	}
}


static void markername( char *out, file_tag type, file_tag id, int team )
{
	sprintf( out, "%s  -  ", tagstr(id) );
	tag_entry *e = get_entry( type, id );
	if (e)
	{
		strcat( out, get_entry_name( e ) );
	}
	else
	{
		sprintf( out+strlen(out), "<missing %s>", get_singular_name_for_type( type ) );
	}
	if (team >= -1)
	{
		sprintf( out+strlen(out), " (team %d)", team );
	}
}


void TMeshForm::RefreshMarkerPalette()
{
	int i, index;
	
	markerPalette->DeleteAll();
	index = 0;
	int selection = selectedPaletteEntry[selectedPaletteType];
	selectedPaletteEntry[selectedPaletteType] = -1;
	for (i = 0; i < marker_palette.Length(); ++i)
	{
		if (marker_palette[i].type == selectedPaletteType)
		{
			char buf[256];
			switch (selectedPaletteType)
			{
				case _marker_team:
					{
						sprintf( buf, "Team %d", marker_palette[i].team_index );
					}
					break;
					
				case _marker_scenery:
					{
						markername( buf, 'scen', marker_palette[i].tag, -2 );
					}
					break;
				
				case _marker_monster:
					{
						markername( buf, 'unit', marker_palette[i].tag, marker_palette[i].team_index );
					}
					break;
				
				case _marker_sound_source:
					{
						markername( buf, 'amso', marker_palette[i].tag, -2 );
					}
					break;
				
				case _marker_projectile:
					{
						markername( buf, 'proj', marker_palette[i].tag, -2 );
					}
					break;
				
				case _marker_local_projectile_group:
					{
						markername( buf, 'lpgr', marker_palette[i].tag, -2 );
					}
					break;
				
				case _marker_model:
					{
						markername( buf, 'geom', marker_palette[i].tag, -2 );
					}
					break;
				
				case _marker_model_animation:
					{
						markername( buf, 'anim', marker_palette[i].tag, -2 );
					}
					break;
			}
			
			markerPalette->Insert( index, buf );
			if (i == selection)
			{
				markerPalette->SetValue( index );
				selectedPaletteEntry[selectedPaletteType] = i;
			}
			++index;
		}
	}
}


marker_data *TMeshForm::CreateNewMarker()
{
	if (selectedPaletteType >= 0 && selectedPaletteType < NUMBER_OF_MARKER_TYPES)
	{
		int e = selectedPaletteEntry[selectedPaletteType];
		if (e >= 0 && e < marker_palette.Length() && marker_palette[e].type == selectedPaletteType)
		{
			marker_data m;
			memset( &m, 0, sizeof(m) );
			m.type = selectedPaletteType;
			m.palette_index = e;
			m.identifier = next_identifier++;
			markers += m;
			return &(markers[markers.Length()-1]);
		}
	}
	
	return NULL;
}


// ========================================  TAG NEEDS UPDATING  ==================================================
bool TMeshForm::NeedCommit()
{
	return fModified;
}


void TMeshForm::free_all()
{
	delete [] cells;
	cells = 0;
	
	marker_palette.DeleteAll();
	markers.DeleteAll();
	
	free( media_coverage_region );
	media_coverage_region = 0;
	
	free( mesh_LOD_data );
	mesh_LOD_data = 0;
	
	free( connector_data );
	connector_data = 0;
	
	free( map_action_data );
	map_action_data = 0;
}


inline void TMeshForm::menucontrol( int id, const struct XGSMenuStatus *e )
{
	SetEnabled( id, e->enable );
	SetFlag( id, e->check );
}


long TMeshForm::DoGetMenuStatus(long arg, void *ptr)
{
	TMeshView *view = dynamic_cast<TMeshView *>(GetWindow()->FindViewByID(1));
	if (!view) return -1;
	
	XGSMenuStatus *e = (XGSMenuStatus *)ptr;
	
	switch (arg) {
		case KMenuMeshLocalAxis:
			e->enable = true;
			e->check = !view->GetMarkerWorldAxis();
			return 0;
		case KMenuMeshWorldAxis:
			e->enable = true;
			e->check = view->GetMarkerWorldAxis();
			return 0;
		
		case KMenuMeshShowColormap:
			e->enable = true;
			e->check = view->GetShowMeshPolys();
			return 0;
		case KMenuMeshShowTerrainType:
			e->enable = (currentMeshTool != _mesh_tool_terrain);
			e->check = view->GetShowTerrainType();
			menucontrol( 173, e );
			return 0;
		
		case KMenuMeshShowRenderGrid:
			e->enable = (_cell_render_height != view->GetClickableMeshHeight());
			e->check = view->GetGridVisibility( _cell_render_height );
			menucontrol( 170, e );
			return 0;
		case KMenuMeshShowPhysicsGrid:
			e->enable = (_cell_physical_height != view->GetClickableMeshHeight());
			e->check = view->GetGridVisibility( _cell_physical_height );
			menucontrol( 171, e );
			return 0;
		case KMenuMeshShowMediaGrid:
			e->enable = (_cell_media_height != view->GetClickableMeshHeight());
			e->check = view->GetGridVisibility( _cell_media_height );
			menucontrol( 172, e );
			return 0;
		
		case KMenuMeshClickRenderGrid:
			e->enable = true;
			e->check = (_cell_render_height == view->GetClickableMeshHeight());
			menucontrol( 150, e );
			return 0;
		case KMenuMeshClickPhysicsGrid:
			e->enable = true;
			e->check = (_cell_physical_height == view->GetClickableMeshHeight());
			menucontrol( 151, e );
			return 0;
		case KMenuMeshClickMediaGrid:
			e->enable = true;
			e->check = (_cell_media_height == view->GetClickableMeshHeight());
			menucontrol( 152, e );
			return 0;
		case KMenuMeshClickNone:
			e->enable = true;
			e->check = (_cell_height_none == view->GetClickableMeshHeight());
			menucontrol( 153, e );
			return 0;
		
		case KMenuMeshModifyRenderGrid:
			e->enable = (_cell_render_height != view->GetClickableMeshHeight());
			e->check = view->GetMeshHeightMask( _cell_render_height );
			menucontrol( 160, e );
			return 0;
		case KMenuMeshModifyPhysicsGrid:
			e->enable = (_cell_physical_height != view->GetClickableMeshHeight());
			e->check = view->GetMeshHeightMask( _cell_physical_height );
			menucontrol( 161, e );
			return 0;
		case KMenuMeshModifyMediaGrid:
			e->enable = (_cell_media_height != view->GetClickableMeshHeight());
			e->check = view->GetMeshHeightMask( _cell_media_height );
			menucontrol( 162, e );
			return 0;
		case KMenuMeshModifyMarkers:
			e->enable = view->GetMeshHeightMask( _cell_physical_height );
			e->check = view->moveMarkersWithMesh;
			menucontrol( 163, e );
			return 0;
		
		case KMenuMeshMarkersOff:
		case KMenuMeshMarkersOnly:
		case KMenuMeshBoundingBoxes:
		case KMenuMeshSimpleModels:
		case KMenuMeshComplexModels:
			e->enable = true;
			e->check = (arg == view->GetMarkerViewMode());
			return 0;
		case KMenuMeshEditHeader:
			e->enable = true;
			return 0;
		case KMenuMeshNewColormap:
			e->enable = (get_entry( 'txst', header.landscape_texturestack_tag ) == 0);
			return 0;
		case KMenuMeshReplaceColormap:
			e->enable = (get_entry( 'txst', header.landscape_texturestack_tag ) != 0);
			return 0;
		case KMenuMeshCollapseDetail:
			e->enable = false;
			return 0;
		case KMenuMeshZeroPitchRoll:
			e->enable = true;
			return 0;
		case KMenuMeshCopyPhysicalToRender:
			e->enable = true;
			return 0;
		case KXGEditClear:
			e->enable = (view->markerSelect.Size() > 0);
			return 0;
		default:
			break;
	}
	return -1;
}


long TMeshForm::DoMenuCommand(long arg, void *)
{
	TMeshView *view = dynamic_cast<TMeshView *>(GetWindow()->FindViewByID(1));
	if (!view) return -1;
	int i, j;
	
	switch (arg) {
		case KMenuMeshLocalAxis:
		case KMenuMeshWorldAxis:
			view->SetMarkerWorldAxis( arg == KMenuMeshWorldAxis );
			return 0;
		case KMenuMeshShowColormap:
			view->SetShowMeshPolys( !view->GetShowMeshPolys() );
			return 0;
		case KMenuMeshShowTerrainType:
			view->SetShowTerrainType( !view->GetShowTerrainType() );
			return 0;
		
		case KMenuMeshShowRenderGrid:
			view->SetGridVisibility( _cell_render_height, !view->GetGridVisibility( _cell_render_height ) );
			return 0;
		case KMenuMeshShowPhysicsGrid:
			view->SetGridVisibility( _cell_physical_height, !view->GetGridVisibility( _cell_physical_height ) );
			return 0;
		case KMenuMeshShowMediaGrid:
			view->SetGridVisibility( _cell_media_height, !view->GetGridVisibility( _cell_media_height ) );
			return 0;
			
		case KMenuMeshClickRenderGrid:
			if (!view->GetMeshHeightMask( _cell_render_height ))
			{
				if (!view->GetMeshHeightMask( _cell_physical_height ))
				{
					view->ClearMeshHeightChangeMask();
					view->SetMeshHeightChangeMask( _cell_render_height, true );
					view->SetMeshHeightChangeMask( _cell_physical_height, true );
				}
				else
				{
					view->ClearMeshHeightChangeMask();
					view->SetMeshHeightChangeMask( _cell_render_height, true );
				}
			}
			view->SetClickableMeshHeight( _cell_render_height );
			return 0;
		case KMenuMeshClickPhysicsGrid:
			if (!view->GetMeshHeightMask( _cell_physical_height ))
			{
				if (!view->GetMeshHeightMask( _cell_render_height ))
				{
					view->ClearMeshHeightChangeMask();
					view->SetMeshHeightChangeMask( _cell_physical_height, true );
					view->SetMeshHeightChangeMask( _cell_render_height, true );
				}
				else
				{
					view->ClearMeshHeightChangeMask();
					view->SetMeshHeightChangeMask( _cell_physical_height, true );
				}
			}
			view->SetClickableMeshHeight( _cell_physical_height );
			return 0;
		case KMenuMeshClickMediaGrid:
			if (!view->GetMeshHeightMask( _cell_media_height ))
			{
				view->ClearMeshHeightChangeMask();
				view->SetMeshHeightChangeMask( _cell_media_height, true );
			}
			view->SetClickableMeshHeight( _cell_media_height );
			return 0;
		case KMenuMeshClickNone:
			view->SetClickableMeshHeight( _cell_height_none );
			return 0;
		
		case KMenuMeshModifyRenderGrid:
			if (view->GetClickableMeshHeight() != _cell_render_height )
			{
				view->SetMeshHeightChangeMask( _cell_render_height, !view->GetMeshHeightMask( _cell_render_height ) );
			}
			return 0;
		case KMenuMeshModifyPhysicsGrid:
			if (view->GetClickableMeshHeight() != _cell_physical_height )
			{
				view->SetMeshHeightChangeMask( _cell_physical_height, !view->GetMeshHeightMask( _cell_physical_height ) );
			}
			return 0;
		case KMenuMeshModifyMediaGrid:
			if (view->GetClickableMeshHeight() != _cell_media_height )
			{
				view->SetMeshHeightChangeMask( _cell_media_height, !view->GetMeshHeightMask( _cell_media_height ) );
			}
			return 0;
		case KMenuMeshModifyMarkers:
			view->moveMarkersWithMesh = !view->moveMarkersWithMesh;
			return 0;
		
		case KMenuMeshMarkersOff:
		case KMenuMeshMarkersOnly:
		case KMenuMeshBoundingBoxes:
		case KMenuMeshSimpleModels:
		case KMenuMeshComplexModels:
			view->SetMarkerViewMode( arg );
			return 0;
		case KMenuMeshEditHeader:
			return 0;
		case KMenuMeshNewColormap:
			return 0;
		case KMenuMeshReplaceColormap:
			return 0;
		case KMenuMeshCollapseDetail:
			return 0;
		case KMenuMeshZeroPitchRoll:
			for (int i = 0; i < markers.Length(); ++i)
			{
				markers[i].pitch = 0;
				markers[i].roll = 0;
				view->MarkerChanged(i);
			}
			view->InvalView();
			fModified = true;
			return 0;
		case KMenuMeshCopyPhysicalToRender:
			for (int y = 0; y < height; ++y)
			{
				for (int x = 0; x < width; ++x)
				{
					mesh_cell *c = cells + y*width + x;
					c->render_height = c->physical_height;
				}
			}
			view->InvalView();
			fModified = true;
			return 0;
		case KXGEditClear:
			{
				if (view->markerSelect.Size() > 0)
				{
					int count = view->markerSelect.Size();
					int *indices = new int[count];
					for (i = 0; i < count; ++i)
					{
						indices[i] = view->markerSelect[i] - &(markers[0]);
						for (j = i; j > 0 && indices[j] > indices[j-1]; --j)
						{
							int swap = indices[j];
							indices[j] = indices[j-1];
							indices[j-1] = swap;
						}
					}
					for (i = 0; i < count; ++i)
					{
						markers.Delete( indices[i] );
					}
					delete [] indices;
					view->ClearSelection();
					view->ResetMarkers();
					fModified = true;
				}
				view->InvalView();
				return 0;
			}
			break;
		default:
			break;
	}
	return -1;
}



long TMeshForm::DoKeyEvent( long arg, void * )
{
	short mods;
	short key;
	
	key = GETLOWORD(arg);
	mods = GETHIWORD(arg);
	float move_x = 0.0f, move_y = 0.0f;
	
	if (mods & KSpecialKey)
	{
		switch (key)
		{
			case KKeyDelete:
				return DoMenuCommand( KXGEditClear, 0 );
			default:
				return -1;
		}
	}
	else
	{
		switch (tolower(key))
		{
			case 'w':
				move_y += 1.0f;
				break;
			case 's':
				move_y -= 1.0f;
				break;
			case 'a':
				move_x -= 1.0f;
				break;
			case 'd':
				move_x += 1.0f;
				break;
			case 'q':
				view_yaw -= 5.0f;
				break;
			case 'e':
				view_yaw += 5.0f;
				break;
			case 'c':
				view_dist *= 0.9f;
				break;
			case 'v':
				view_dist /= 0.9f;
				break;
			case 'r':
				view_pitch += 5.0f;
				break;
			case 'f':
				view_pitch -= 5.0f;
				break;
			case 0x08:
				return DoMenuCommand( KXGEditClear, 0 );
			default:
				return -1;
		}
	}
	
	vec3 flat_forward = view_forward;
	vec3 flat_side = view_right;
	flat_forward.z = flat_side.z = 0.0f;
	flat_forward = normalized(flat_forward);
	flat_side = normalized(flat_side);
	if (view_pitch < 10.0f) view_pitch = 10.0f;
	if (view_pitch > 85.0f) view_pitch = 85.0f;
	while (view_yaw < 0.0f) view_yaw += 360.0f;
	while (view_yaw > 360.0f) view_yaw -= 360.0f;
	if (view_dist < 10.0f) view_dist = 10.0f;
	if (view_dist > 2 * (width + height)) view_dist = 2 * (width + height);
	view_focus += view_dist * 0.01 * (move_y * flat_forward + move_x * flat_side);
	UpdateViewData();
	if (mesh_view) {
		mesh_view->InvalView();
	}
	return 0;
}


void TMeshForm::UpdateViewData()
{
	if (view_focus.x < 0.0f) view_focus.x = 0.0f;
	if (view_focus.x >= width-2) view_focus.x = width-2;
	if (view_focus.y < 0.0f) view_focus.y = 0.0f;
	if (view_focus.y >= height-2) view_focus.y = height-2;
	
	int cellx = view_focus.x;
	int celly = view_focus.y;
	float fracx = view_focus.x - floor(view_focus.x);
	float fracy = view_focus.y - floor(view_focus.y);
	
	float h00 = get_cell( cellx, celly )->physical_height / 512.0;
	float h10 = get_cell( cellx+1, celly )->physical_height / 512.0;
	float h01 = get_cell( cellx, celly+1 )->physical_height / 512.0;
	float h11 = get_cell( cellx+1, celly+1 )->physical_height / 512.0;
	view_focus.z = fracx * fracy * h11 +
					fracx * (1.0f - fracy) * h10 +
					(1.0f - fracx) * fracy * h01 +
					(1.0f - fracx) * (1.0f - fracy) * h00;
	
	float sp = sin(view_pitch * 3.1415926535/180.0);
	float cp = cos(view_pitch * 3.1415926535/180.0);
	float sy = sin(view_yaw * 3.1415926535/180.0);
	float cy = cos(view_yaw * 3.1415926535/180.0);
	
	view_forward.x = cy * -cp;
	view_forward.y = sy * -cp;
	view_forward.z = -sp;
	view_up.x = cy * sp;
	view_up.y = sy * sp;
	view_up.z = -cp;
	view_right = cross( view_up, view_forward );
	view_origin = view_focus - view_dist * view_forward;
}


void TMeshForm::HandleTagChanged( tag type, tag id )
{
	TMeshView *mesh_view = dynamic_cast<TMeshView *>(GetWindow()->FindViewByID(1));
	if (!mesh_view) return;
	
	switch (type) {
		case 'txst':
			mesh_view->ResetTexture( id );
			mesh_view->InvalView();
			break;
		case 'skmd':
			mesh_view->ResetSkelmodel( id );
			mesh_view->InvalView();
			break;
	}
}


long TMeshForm::MarkerPaletteDlogProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	marker_palette_dlog_info *info = (marker_palette_dlog_info *)data;
	marker_palette_entry &m = info->m;
	static const file_tag types[] = 
	{
		-1, 'scen', -1, 'unit', -1, 'amso', 'geom', -1, -1, 'proj', 'lpgr', 'anim'
	};
	int i;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						::SetTag( dlog, 100, types[m.type], m.tag );
						dlog->SetValue( 400, m.team_index );
						for (i = 0; i < 6; ++i)
						{
							dlog->SetValue( 200 + i, (m.flags >> i) & 1 );
						}
						for (i = 0; i < 14; ++i)
						{
							dlog->SetValue( 300 + i, (m.extra_flags >> i) & 1 );
						}
					} return 0;
			} break;
		case KXGKeyClick:
			switch (arg) {
				case 400:
					return 0;
			} break;
		case KXGControlClick:
			if (arg >= 200 && arg <= 205 || arg >= 300 && arg <= 313)
			{
				dlog->SetValue( arg, !dlog->GetValue(arg) );
			}
			else
			{
				switch (arg) {
					case 100:
						{
							tag new_tag = pick_tag_of_type( types[m.type], m.tag, info->f );
							if (new_tag)
							{
								::SetTag( dlog, 100, types[m.type], new_tag );
							}
						}
						return 0;
					case 101:
						{
							tag_entry *e = get_entry( types[m.type], ::GetTag( dlog, 100 ) );
							if (e)
							{
								TRevengeApp::OpenItem( e );
							}
						}
						return 0;
					case 1:
						m.tag = ::GetTag( dlog, 100 );
						m.team_index = dlog->GetValue( 400 );
						m.flags = 0;
						for (i = 0; i < 6; ++i)
						{
							if (dlog->GetValue( 200 + i )) m.flags |= (1 << i);
						}
						m.extra_flags = 0;
						for (i = 0; i < 14; ++i)
						{
							if (dlog->GetValue( 300 + i )) m.extra_flags |= (1 << i);
						}
						dlog->EndDialog( 1 );
						return 0;
					case 2:
						dlog->EndDialog( 0 );
						return 0;
				}
			} break;
	}
	
	return -1;
}


void TMeshForm::SetCurrentTool( int newTool )
{
	if (newTool < _mesh_tool_first_index || newTool > _mesh_tool_last_index) newTool = _mesh_tool_first_index;
	
	for (int i = _mesh_tool_first_index; i <= _mesh_tool_last_index; ++i)
	{
		SetInt( i, i == newTool );
	}
	
	int palette = -1;
	
	switch (newTool)
	{
		case _mesh_tool_band_grid:
		case _mesh_tool_band_markers:
			palette = 200;
			break;
		
		case _mesh_tool_water:
			palette = 201;
			break;
		
		case _mesh_tool_terrain:
			palette = 202;
			break;
		
		case _mesh_tool_pull_grid:
		case _mesh_tool_smooth_grid:
			palette = 203;
			break;
	}
	
	for (int i = 200; i <= 204; ++i)
	{
		SetVisible( i, i == palette );
	}
	
	currentMeshTool = newTool;
}


void TMeshForm::fix_vertex_media_flag( int x, int y )
{
	struct mesh_cell *c = get_cell( x, y );
	if (c)
	{
		c->flags &= ~_mesh_vertex_is_media_flag;
		for (int dx = -1; dx <= 0; ++dx)
		{
			for (int dy = -1; dy <= 0; ++dy)
			{
				struct mesh_cell *d = get_cell( x + dx, y + dy );
				if (d && AT_LEAST_PARTIALLY_MEDIA(d))
				{
					c->flags |= _mesh_vertex_is_media_flag;
				}
			}
		}
	}
}


