=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed October 2012 by Fredo6

# Permission to use this software for any purpose and without fee is hereby granted
# Distribution of this software for commercial purpose is subject to:
#  - the expressed, written consent of the author
#  - the inclusion of the present copyright notice in all copies.

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#-----------------------------------------------------------------------------
# Name			:   TopoShaper_Algo.rb
# Original Date	:   20 Oct 2012 - version 1.0
# Description	:   TopoShaper Algorithms
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_TopoShaper

#=============================================================================================
#=============================================================================================
# Public API: Implement the public API for Calculation
#=============================================================================================
#=============================================================================================

#API: Formal service API for Isocontour calculation
def F6_TopoShaper.api_isocontour_calculation_protected(lst_contours, hsh_options=nil)
	algo = TopoShaperAlgo.new
	begin
		results = algo.api_calculation lst_contours, hsh_options
	rescue Exception => e
		results = Struct.new(:error).new
		results.error = e
	end	
	results
end

#API: Just an example for illustration
def F6_TopoShaper.api_example
	selection = Sketchup.active_model.selection
	return if selection.empty? || selection.length > 1 
	g = selection[0]
	return unless G6.is_grouponent?(g)
	entities = G6.grouponent_entities(g)
	
	hcurves = {}
	edges = entities.grep(Sketchup::Edge)
	edges.each do |edge|
		curve = edge.curve
		next unless curve
		hcurves[curve.entityID] = curve
	end
	return if hcurves.empty?
	
	tr = g.transformation
	lst_contours = hcurves.values.collect { |curve| curve.vertices.collect { |vx| tr * vx.position } }
	
	notify_proc = proc { |time, message| puts "Time = #{time} msg = #{message}" }
	hsh_options = { :nx => 50, :notify_proc => notify_proc }
	
	results = F6_TopoShaper.api_isocontour_calculation lst_contours, hsh_options	
	F6_TopoShaper.api_show_results(results)
	
	#Error
	if results.error
		puts "error #{results.error.message}"
		return
	end	
	
	model = Sketchup.active_model
	model.start_operation "API Topo"
	grp = model.active_entities.add_group
	gent = grp.entities
	results.lst_cell_info.each do |cell_info|
		pts, diago = cell_info
		if diago
			f = gent.add_face(pts[0..2])
			f.material = 'yellow' 
			f = gent.add_face(pts[2], pts[3], pts[0])
			f.material = 'yellow' 
		else
			f = gent.add_face(pts)
			f.material = 'yellow' 
		end	
	end	
	
	results.skirt_panels.each do |pts|
		f = gent.add_face(pts)
		f.material = 'brown' 
	end
	
	gent.add_curve results.hull_projection
	
	model.commit_operation

	nil
end

#=============================================================================================
#=============================================================================================
# Class TopoShaperAlgo: Implement the surface reconstruction algorithm
#=============================================================================================
#=============================================================================================

class TopoShaperAlgo

#Include the mixin for common operations on contours
include TopoShaperContour_mixin
	
#Initialize the structure	
def init_struct	
	#Structure for grid nodes
	@struct_Grid_Node = Struct.new :ix, :iy, :x, :y, :pt, :ptxy, :zone, :inside_hull, :type, :contours,
								   :node_friends, :shiftedX, :shiftedY, :direct_neighbours, :reasons, :cells,
					               :boite, :ref_contour

	#Structure for Zones
	@struct_Grid_Zone = Struct.new :lsk, :contours

	#Structure for Cells
	@struct_Grid_Cell = Struct.new :nodes, :ptsxy, :inside_hull, :pts, :even, :triangles, :planes, :diago, :triangles_xy,
                                   :polygon, :lines, :ipos_tri, :ipos_lines
					   
	#Structure for boites
	@struct_Topo_Boite = Struct.new :bb, :key, :faces_3du, :faces_3d, :faces_2d, :nodes, :cells, :panels				 
					   
	#Structure for API results
	@struct_API_Results = Struct.new :error, :nx, :ny, :lst_cell_info, :skirt_panels, :hull_projection
	
end
	
#INIT: Initialization of the class instance with contours
def initialize()
	init_struct
	
	#Initialize the working environment
	@ngrid_min = 20
	@ngrid_max = 200		
end

#INIT: Initialization for interactive processing
def initialize_interactive(tool, hsh_options=nil, *hargs)
	@model = Sketchup.active_model
	@view = Sketchup.active_model.active_view
	@myclass = :algo
	@tool = tool
	@hsh_options = hsh_options
	
	@linked = true
	
	#Assigning the options
	@option_hilltop = :auto
	
	#Other initializations
	colors_init
	
	#Parsing arguments
	parse_args hargs
	
	#Loading a pre-existing group if any
	if @group_attr
		@group_attr_invalid = attribute_load @group_attr
		@entity_attr = @group_attr.entityID	
	end	
	
	#Initialize the VCB
	init_VCB
end

#Check if the Algo is valid, which may not be the case when loading from an existing terrain group
def creation_invalid? ; @group_attr_invalid ; end

#INIT: Parse arguments for interactive initialization
def parse_args(hargs)
	hargs.each do |hsh|
		next unless hsh.class == Hash
		hsh.each do |key, val|
			case key
			when :palette
				@palette = val
			when :group_attr
				@group_attr = val
			when :tr_attr
				@tr_attr = val
			when :lst_pts
				@lst_pts = val
			when :plane_normal
				@plane_normal = val
			end	
		end
	end	
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# API: Method for silent API
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#API: Top method to calculate the terrain from isocontours
def api_calculation(lst_contours, hsh_options=nil)
	@time_start_api = Time.now
	
	#No contours
	return nil if lst_contours.empty?
	
	#Default options
	@nx = 50
	@plane_normal = Z_AXIS
	@option_hilltop = :auto
	
	#Parsing specified options
	if hsh_options && hsh_options.class == Hash
		hsh_options.each do |key, val|
			case key
			when :nx
				@nx = val
			when :ny
				@ny = val
			when :plane_normal
				@plane_normal = val
			when :option_hilltop
				@option_hilltop = val
			when :notify_proc
				@api_notify_proc = val
			end	
		end
	end	

	#Analyzing contours
	api_notify :analyze_contours
	contour_analysis lst_contours

	#Calculating the Hull
	api_notify :compute_hull
	hull_calculate
	
	#Calculating the Grid
	api_notify :generate_grid
	@nx, @ny = grid_linked_dimension @nx, @ny
	grid_construction
	cell_construction
	grid_mark_contours
	
	#calculating the zones
	api_notify :calculate_zones
	zone_exploration
	
	#Calculating methods for nodes
	top_method
	
	#Treatment of hilltop
	@lst_contours.each do |contour|
		contour.option_hilltop = @option_hilltop if contour.single
	end	
	
	#Interpolating nodes multi
	api_notify :altitude_multi
	@nodes_multi.each { |node| altitude_multi node unless node.pt }

	#Extrapolating nodes single
	api_notify :altitude_single
	@nodes_single.each { |node| altitude_single node if node.type == :single }

	#Calculating the boundaries
	bounds_calculation
	
	#Calculating the cells
	api_notify :calculate_cell
	lst_cells_geom = []
	@lst_cells.each { |cell| lst_cells_geom.push cell if cell_compute(cell) }
	
	#Calculating the wall
	api_notify :calculate_bounds
	wall_calculation
	
	#Returning the result information
	results = @struct_API_Results.new
	results.nx = @nx
	results.ny = @ny
	results.hull_projection = @hull_pts.collect { |pt| @tr_tot_inv * pt }
	results.skirt_panels = @wall_faces.collect { |pts| pts.collect { |pt| @tr_tot_inv * pt } }
	lcell_info = []
	lst_cells_geom.each do |cell|
		lpt = cell.pts.collect { |pt| @tr_tot_inv * pt }
		lcell_info.push((cell.triangles.length > 1) ? [lpt, true] : [lpt])
	end	
	results.lst_cell_info = lcell_info
	results
end

def api_notify(code)
	@api_notify_proc.call [Time.now - @time_start_api, T6[("VBAR_#{code}").intern]] if @api_notify_proc
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# HILLTOP: Hilltop management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#HILLTOP: Change the option for hilltop, flat or auto
def option_change_hilltop(option_hilltop, contours=nil)
	#Assigning to the contours
	change_contours = []
	lst_contours = (contours) ? contours : @lst_contours
	lst_contours.each do |contour|
		next unless contour.single && contour.option_hilltop != option_hilltop
		contour.option_hilltop = option_hilltop
		change_contours[contour.inum] = true
	end	
	return if change_contours.compact.length == 0
	
	#Calculating the nodes to be recalculated
	change_nodes = []
	@nodes_single.each do |node|
		ik = node.ref_contour.inum
		change_nodes.push node if change_contours[ik]
	end
	
	#Recalculating the global option status
	option_recompute_global
	
	#Recalculating the altitudes
	top_updating nil, change_nodes
end

def option_recompute_global
	flat = @lst_contours.find { |contour| contour.single && contour.option_hilltop == :flat }
	auto = @lst_contours.find { |contour| contour.single && contour.option_hilltop != :flat }
	if flat && auto
		@option_hilltop = nil
	elsif flat
		@option_hilltop = :flat
	else	
		@option_hilltop = :auto
	end
end

def option_hilltop_possible? ; @option_hilltop_possible ; end
def option_hilltop? ; @option_hilltop ; end
def option_hilltop_valid_selected_contours?
	@lst_contours.find_all { |contour| contour.selected && contour.single }
end
def option_hilltop_possible_on_selected?
	@option_hilltop_possible && @lst_contours.find { |contour| contour.selected && contour.single }
end
def option_hilltop_value_on_selected
	lst_contours = option_hilltop_valid_selected_contours?
	flat = lst_contours.find { |contour| contour.option_hilltop == :flat }
	auto = lst_contours.find { |contour| contour.option_hilltop == :auto }
	if flat && auto
		return nil
	elsif flat
		return :flat
	end
	:auto	
end	

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# CLIFF: Cliff management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#CLIFF: Change the option for cliffing at contour
def option_change_cliff(option_cliff, contours=nil)
	#Assigning to the contours
	change_contours = []
	lst_contours = (contours) ? contours : @lst_contours
	lst_contours.each do |contour|
		next unless contour.option_cliff != option_cliff
		contour.option_cliff = option_cliff
		change_contours[contour.inum] = contour.inum
	end	
	change_contours = change_contours.compact
	return if change_contours.empty?
	
	#Calculating the nodes to be recalculated
	change_nodes_multi = []
	change_nodes_single = []
	@grid_nodes.each do |node|
		next unless node.type == :multi || node.type == :single 
		lsk = node.contours.collect { |contour| contour.inum }
		lsk.push node.ref_contour.inum if node.type == :single
		unless (lsk & change_contours).empty?
			(node.type == :single) ? change_nodes_single.push(node) : change_nodes_multi.push(node)
		end	
	end
	return if change_nodes_multi.empty? && change_nodes_single.empty?
	
	#Recalculating the altitudes
	top_updating change_nodes_multi, change_nodes_single
end

def option_cliff_value_on_selected
	:auto	
end	

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# Top level routines
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#TOP: Toplevel method for processing calculation
def top_processing
	vbar_start :full_processing
	
	#Cleanup from previous session if required
	edgewire_erase
	
	#Analyzing contours and node methods
	top_analysis_contour
	top_method	
	option_recompute_global
	
	#Interpolating nodes multi
	vbar_progression :altitude_multi
	n = @nodes_multi.length
	@nodes_multi.each_with_index do |node, i|	
		vbar_mini_progression(n, i, 2000, 200)
		altitude_multi node unless node.pt
	end	

	#Extrapolating nodes single
	vbar_progression :altitude_single
	nodes = @nodes_single
	n = nodes.length
	nodes.each_with_index do |node, i|
		vbar_mini_progression(n, i, 100, 100)
		altitude_single node if node.type == :single
	end	

	#Calculating the boundaries
	bounds_calculation
	
	#Calculating the cells
	vbar_progression :calculate_cell
	@lst_cells_geom = []
	n = @lst_cells.length
	@lst_cells.each_with_index do |cell, i|
		vbar_mini_progression(n, i, 2000, 500)
		@lst_cells_geom.push cell if cell_compute(cell)
	end	
		
	#Calculating the wall
	wall_calculation
	
	#Instantiating the boites
	bounds_instantiation
	
	#Generating the working views
	camera_compute
	
	#Generating the dessin
	dessin_compute
	
	vbar_stop
	compute_infobox

end

def top_updating(nodes_multi, nodes_single)
	hsh_cells = {}
	vbar_start :updating
	
	edgewire_erase
	
	#Recomputing the nodes multi if any
	if nodes_multi
		vbar_progression :altitude_multi
		n = nodes_multi.length
		nodes_multi.each_with_index do |node, i|
			vbar_mini_progression(n, i, 100, 100)
			altitude_multi node if node.type == :multi
			node.cells.each { |cell| hsh_cells[cell.object_id] = cell }
		end
	end	
	
	#Recomputing the nodes single if any
	if nodes_single
		vbar_progression :altitude_single
		n = nodes_single.length
		nodes_single.each_with_index do |node, i|
			vbar_mini_progression(n, i, 100, 100)
			altitude_single node if node.type == :single
			node.cells.each { |cell| hsh_cells[cell.object_id] = cell }
		end
	end	
	
	#Calculating the boundaries
	bounds_calculation
	
	#Recalculating the cells	
	vbar_progression :update_cell
	lst_cells = hsh_cells.values
	n = lst_cells.length
	lst_cells.each_with_index do |cell, i|
		vbar_mini_progression(n, i, 2000, 500)
		cell_compute(cell)
	end	
	
	#Calculating the wall
	wall_calculation
	
	#Generating the dessin
	dessin_update lst_cells
	
	vbar_stop
	compute_infobox	
end

def top_analysis_contour
	#Analyze the contours (done once)
	vbar_progression :analyze_contours
	contour_analysis @lst_pts unless @lst_contours
	
	#Calculating and adjusting the hull enveloppe (done once)
	vbar_progression :compute_hull
	hull_calculate unless @hull_pts
	
	#Generate the grid
	vbar_progression :generate_grid
	@nx, @ny = grid_linked_dimension @nx, @ny
	grid_construction
	cell_construction

	#Sampling the contours on the grid
	vbar_progression :sampling_grid
	grid_mark_contours
	
	#Determine the zones between contours
	vbar_progression :calculate_zones
	zone_exploration
end

#TOP: Top method to resolve the nodes
def top_method
	@proximity_nk = 0.05 * (@dx + @dy)
	@uncomputed_nodes = []
	@nodes_multi = []
	@nodes_single = []
	@nodes_on_contour = []
	
	vbar_progression :method_multi
	method_multi
	
	vbar_progression :method_border
	method_hull_border
	
	#Computing the single nodes
	vbar_progression :method_single
	@uncomputed_nodes = @uncomputed_nodes.find_all { |nd| !nd.type && nd.inside_hull}
	n = @uncomputed_nodes.length
	@uncomputed_nodes.each_with_index do |node, i|
		vbar_mini_progression(n, i, 40, 20)		
		method_single node if node.inside_hull
	end
end

#TOP: Return the final contour points to be used for terrain
def get_lst_pts 
	@lst_contours.collect { |contour| contour.pts.collect { |pt| @tr_tot_inv * pt } }
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# INFO: Message and tip calculation
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#INFO: Calculate the text for the palette information box and notify the tool
def compute_infobox
	infobox1 = T6[:INFO_ContourEdges, @lst_contours.length, @total_nb_edges]
	infobox2 = T6[:INFO_CalculationTime, sprintf("%0.2f", @time_calculation)]
	@infobox = [infobox1, infobox2]
	@tool.notify :infobox, *@infobox
end

#INFO: Compute the message for display in the palette
def palette_message
	text = ""
	text += "Altitude = #{@current_altitude}" if @current_altitude
	text
end

#INFO: check if the floating palette can e shown
def show_floating_palette?
	@lst_contours && @lst_contours.find { |contour| contour.selected }
end

#INFO: Contextual menu
def contextual_menu_contribution(cxmenu)	
	#Menu for contour selection
	cxmenu.add_sepa
	
	#Contour selection
	if @hi_contour
		text = (@hi_contour.selected) ? T6[:MNU_UnSelectContour] : T6[:MNU_SelectContour]
		cxmenu.add_item(text) { contour_toggle_select @hi_contour}
	end

	#Contour hilltop
	contextual_menu_hilltop(cxmenu)
	
end

#INFO: Contextual menu item for Cliffing
def contextual_menu_hilltop(cxmenu)
	return unless @option_hilltop_possible
	
	#Option for selected contours and highlighted contour
	cxmenu.add_sepa	
	lst_contours = option_hilltop_valid_selected_contours?
	if lst_contours.length > 0
		flat = lst_contours.find { |contour| contour.option_hilltop == :flat }
		auto = lst_contours.find { |contour| contour.option_hilltop == :auto }
		if flat && auto
			cxmenu.add_item(T6[:TIP_HilltopFlat]) { notify_contour_set :hilltop, :flat, lst_contours }
			cxmenu.add_item(T6[:TIP_HilltopAuto]) { notify_contour_set :hilltop, :auto, lst_contours }
		elsif flat	
			cxmenu.add_item(T6[:TIP_HilltopAuto]) { notify_contour_set :hilltop, :auto, lst_contours }
		else	
			cxmenu.add_item(T6[:TIP_HilltopFlat]) { notify_contour_set :hilltop, :flat, lst_contours }
		end
	elsif @hi_contour && @hi_contour.single
		if @hi_contour.option_hilltop == :flat
			cxmenu.add_item(T6[:TIP_HilltopAuto]) { notify_contour_set :hilltop, :auto, [@hi_contour] }
		else	
			cxmenu.add_item(T6[:TIP_HilltopFlat]) { notify_contour_set :hilltop, :flat, [@hi_contour] }
		end
	end	
	
	#Options for all contours
	cxmenu.add_sepa	
	cxmenu.add_item(T6[:TIP_HilltopFlatAll]) { notify_contour_set :hilltop, :flat }
	cxmenu.add_item(T6[:TIP_HilltopAutoAll]) { notify_contour_set :hilltop, :auto }
end

#INFO: Contextual menu item for Cliffing
def contextual_menu_add_cliff(cxmenu, contour=nil)
	cxmenu.add_item(T6[:TIP_CliffNO]) { notify_contour_set :cliff, 0, contour }
	cxmenu.add_item(T6[:TIP_CliffTrue]) { notify_contour_set :cliff, 100, contour }
end

#INFO: methods to check the state of selectio and ignore / include for contours
def contour_some_selected? ; @lst_contours && @lst_contours.find { |contour| contour.selected } ; end

#CONTOUR: Compute box information for contour
def contour_compute_boxinfo(contour)
	ik = contour.inum
	nb_edges = contour.pts.length-1
	text = "##{ik} - Edges = #{nb_edges} Alt = #{Sketchup.format_length(contour.altitude)}"
	contour.boxinfo = text
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# BOUNDS: Boundaries and boite Management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#BOUNDS: Calculate the boites
def bounds_calculation
	vbar_progression :calculate_bounds

	#Computing the minimum and maximum altitude
	first_node = @grid_nodes.find { |node| node.pt }
	@zmin = @zmax = first_node.pt.z
	@grid_nodes.each do |node|
		next unless node.pt
		z = node.pt.z
		@zmin = z if z < @zmin
		@zmax = z if z > @zmax
	end
	
	#Calculating the bounding boxes for the Surface
	ptmin = Geom::Point3d.new @xmin, @ymin, @zmin
	ptmax = Geom::Point3d.new @xmax, @ymax, @zmax
	@bb_3d = Geom::BoundingBox.new.add ptmin, ptmax
	
	@bb_surface = Geom::BoundingBox.new
	@bb_surface.add @tr_dessin * ptmin, @tr_dessin * ptmax
	@bb_surface_map = Geom::BoundingBox.new.add @bb_surface, @bb_map
	@bb_extents.add @bb_surface
	
	#Calculating the normalized bounding box
	boite_calculation
	
	#Assigning boites to nodes
	@grid_nodes.each do |node|
		pt = node.pt
		next unless pt
		boite = boite_locate_point(pt)
		node.boite = boite
		boite.nodes = [] unless boite.nodes
		boite.nodes.push node
	end	
end

#BOITES: Instantiation of the boites after they have been filled with cell information
def bounds_instantiation
	vbar_progression :instantiate_bounds

	@lst_boites = []
	@hsh_boites.each do |key, boite| 
		next if boite.bb.empty?
		boite_instantiate boite
		@lst_boites.push boite
	end		
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# WALL: Wall Management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#WALL: Calculate the skirt
def wall_calculation		
	vbar_progression :calculate_wall

	#Preparing the bounding boxes for the wall
	ndiv = 4
	dx = (@xmax - @xmin) / ndiv
	dy = (@ymax - @ymin) / ndiv
	wall_boites = []
	for i in 0..ndiv * ndiv - 1
		boite = wall_boites[i] = @struct_Topo_Boite.new
		boite.bb = Geom::BoundingBox.new
		boite.panels = []
	end
	
	#Calculating the wall polygons
	@wall_faces = []
	@wall_quads = []
	@wall_lines = []
	zmin = (@zmin > 0) ? 0 : @zmin
	for i in 0..@hull_nodes.length-2
		#Computing the wall panel
		pt3 = @hull_nodes[i].pt
		pt2 = @hull_nodes[i+1].pt
		next if pt2 == pt3
		next unless pt2 && pt3
		pt0 = Geom::Point3d.new(pt3.x, pt3.y, zmin)
		pt1 = Geom::Point3d.new(pt2.x, pt2.y, zmin)
		next if pt0 == pt3 && pt1 == pt2
		lw = [pt0, pt1]
		lw.push pt2 if pt2 != pt1
		lw.push pt3 if pt3 != pt0
		@wall_faces.push lw
		@wall_quads.push pt0, pt1, pt2, pt3
		@wall_lines.push pt0, pt1, pt1, pt2, pt2, pt3, pt3, pt0
		
		#Assigning to a boite
		ix = ((pt1.x - @xmin) / dx).floor
		iy = ((pt1.y - @ymin) / dy).floor
		ix = 0 if ix < 0
		iy = 0 if iy < 0
		ix = ndiv - 1 if ix >= ndiv
		iy = ndiv - 1 if iy >= ndiv
		iboite = iy * ndiv + ix
		boite = wall_boites[iboite]
		boite.bb.add pt0, pt1, pt2, pt3
		boite.panels.push [pt0, pt1, pt2, pt3]
		
		ix = ((pt0.x - @xmin) / dx).floor
		iy = ((pt0.y - @ymin) / dy).floor
		ix = 0 if ix < 0
		iy = 0 if iy < 0
		ix = ndiv - 1 if ix >= ndiv
		iy = ndiv - 1 if iy >= ndiv
		iboite2 = iy * ndiv + ix
		if iboite != iboite2
			boite = wall_boites[iboite2]
			boite.bb.add pt0, pt1, pt2, pt3
			boite.panels.push [pt0, pt1, pt2, pt3]
		end	
	end	
	
	#Instantiating the boites
	@wall_boites = []
	wall_boites.each do |boite|
		next if boite.bb.empty?	
		boite_instantiate boite	
		@wall_boites.push boite
	end
end


#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# GRID: Grid Management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#GRID: Compute the dimensions when linked
def grid_linked_dimension(nx=nil, ny=nil)
	nx = 50 unless nx || ny
	if nx && (@linked || !ny)
		ny = (nx * (@ymax - @ymin).to_f / (@xmax - @xmin)).round
	elsif ny  && (@linked || !nx)
		nx = (ny * (@xmax - @xmin).to_f / (@ymax - @ymin)).round
	end
	[nx, ny]
end

#GRID: Notify the redimensioning of the grid from the dialog box
def notify_grid_dimension(hargs)
	nx = hargs[:nx]
	ny = hargs[:ny]
	@linked = hargs[:linked]
	nx = @nx unless @nx
	ny = @ny unless @ny
	if nx != @nx || @ny != ny
		@nx = nx
		@ny = ny
		top_processing
	end	
	@view.invalidate
end

#GRID: Prepare a rectangular grid
def grid_construction(nx=nil, ny=nil)	
	t0 = Time.now
	#Calculating the sampling
	@dx = (@xmax - @xmin) / @nx
	@dy = (@ymax - @ymin) / @ny
	
	#Boites for cell picking
	@nx_boite = @nx / @ndiv_boite
	@ny_boite = @ny / @ndiv_boite
	
	#Creating the grid nodes and lines
	@grid_nodes = []
	for ix in 0..@nx
		xval = @xmin + ix * @dx
		for iy in 0..@ny
			yval = @ymin + iy * @dy
			node = @grid_nodes[iy * (@nx+1) + ix] = @struct_Grid_Node.new
			node.ix = ix
			node.iy = iy
			node.x = xval
			node.y = yval
			node.ptxy = Geom::Point3d.new xval, yval, 0
		end
	end

	#Compute the maximum grid dimensions
	grid_compute_maximum_dimensions
end

#GRID: Return a text with the grid dimensions
def text_grid_dimension
	(@nx) ? "#{@nx} x #{@ny}" : ""
end

def grid_get_dimensions
	[@nx, @ny, @linked]
end

def grid_max_dimensions ; @grid_minmax ; end
def grid_max_dimensions_delta ; @grid_minmax_d ; end
def grid_current_delta ; [(@xmax - @xmin) / @nx, (@ymax - @ymin) / @ny] ; end

#GRID: Compute the maximum dimensions
def grid_compute_maximum_dimensions
	@grid_minmax = []
	ratio = (@xmax - @xmin) / (@ymax - @ymin)
	for iaxis in 0..1
		ratio = 1.0 / ratio if iaxis == 1
		if ratio >= 1
			@grid_minmax.push @ngrid_min
			@grid_minmax.push @ngrid_max
		else
			@grid_minmax.push((@ngrid_min * ratio).ceil)
			@grid_minmax.push((@ngrid_max * ratio).floor)
		end
	end	
	
	dx = @xmax - @xmin
	dy = @xmax - @xmin
	@grid_minmax_d = [dx / @grid_minmax[0], dx / @grid_minmax[1], dy / @grid_minmax[2], dy / @grid_minmax[3]]
end

#GRID: Compute the dimension of cells based on number of cells in the grid
def grid_delta(nx, ny)
	[(@xmax - @xmin) / nx, (@ymax - @ymin) / ny]
end

#GRID: method to check a string value, either number r length
#Returns [status, nx, ny, svalue], with status:
#    0 --> OK
#    1 --> Warning, but values have been capped
#   -1 --> Error, string is invalid
def grid_check_dimension_as_string(iaxis, svalue, nx0=nil, ny0=nil, linked=nil)
	unless nx0
		nx0 = @nx unless nx0
		ny0 = @ny unless ny0
		linked = @linked
	end	
	delta = (iaxis == 0) ? (@xmax - @xmin) : (@ymax - @ymin)
	status = 0
	
	#Parsing as an integer for grid dimension and then as a length dimension
	for ipass in 0..1
		if ipass == 0
			nval = Traductor.string_to_integer_formula svalue
			next unless nval
		else
			len = Traductor.string_to_length_formula svalue
			return [-1, nx0, ny0, svalue] unless len
			nval = (delta / len).round
		end
	
		#Checking the value boundaries
		vmin = @grid_minmax[2*iaxis]
		vmax = @grid_minmax[2*iaxis+1]
		if nval < vmin
			nval = vmin
			status = 1
		elsif nval > vmax
			nval = vmax
			status = 1
		end	
		svalue = "#{nval}"
		
		if linked
			nx, ny = (iaxis == 0) ? grid_linked_dimension(nval) : grid_linked_dimension(nil, nval)
		else
			nx, ny = (iaxis == 0) ? [nval, ny0] : [nx0, nval]
		end

		return [status, nx, ny, svalue]
	end
end

def grid_check_dimension_as_integer(iaxis, nval)
	status = 0
	vmin = @grid_minmax[2*iaxis]
	vmax = @grid_minmax[2*iaxis+1]
	if nval < vmin
		nval = vmin
		status = 1
	elsif nval > vmax
		nval = vmax
		status = 1
	end	
	[status, nval]
end

def grid_check_dimension_as_length(iaxis, lval)
	delta = (iaxis == 0) ? (@xmax - @xmin) : (@ymax - @ymin)
	nval = (delta / lval).round
	grid_check_dimension_as_integer(iaxis, nval)	
end

#GRID: Validate and execute the Grid redimensioing from VCB
def grid_check_dimension_as_VCB(lvalue, text)
	
	if lvalue.length > 2
		UI.beep
		msg = T6[:MSG_Error_GridValue, "'#{text}'"]
		return
	end
	
	nx = ny = nil
	status_x = status_y = 0
	symb, valx = lvalue[0]
	if valx != 0
		status_x, nx = (symb == :nx) ? grid_check_dimension_as_integer(0, valx) : grid_check_dimension_as_length(0, valx)
	end
	symb, valy = lvalue[1]
	if valy && valy != 0
		status_y, ny = (symb == :nx) ? grid_check_dimension_as_integer(1, valy) : grid_check_dimension_as_length(1, valy)
	end
	
	if status_x < 0 || status_y < 0
		msg = T6[:MSG_Error_GridValue, "'#{text}'"]
		@palette.set_message msg, 'E'
	elsif status_x == 1
		msg = T6[:MSG_Error_OutOfRange]
		@palette.set_message msg, 'E'
	elsif status_y == 1
		msg = T6[:MSG_Error_OutOfRange]
		@palette.set_message msg, 'E'
	elsif !nx && !ny
		return
	elsif nx && ny
		linked = false
	elsif @linked
		nx, ny = grid_linked_dimension(nx, ny)
		linked = @linked
	end
	
	#Changing the grid
	hsh = { :nx => nx, :ny => ny, :linked => linked }
	notify_grid_dimension hsh
end

#GRID: Sample a contour on the grid in X and Y
#Info is returned in order [pt, [ix1, iy1], [ix2, iy2]]
def grid_sample_contour(pts)
	grid_pts = []
	for i in 0..pts.length-2
		pt1 = pts[i]
		pt2 = pts[i+1]
		lsp = grid_sample_segment_in_X(pt1, pt2) + grid_sample_segment_in_Y(pt1, pt2)
		lsp = lsp.sort { |a, b| a[1] <=> b[1] }
		grid_pts += lsp
	end
	
	grid_nodes = []
	grid_pts.each do |a|
		pt, d, ls1, ls2 = a
		node1 = node_from_coord(*ls1)
		node2 = (ls2) ? node_from_coord(*ls2) : nil
		grid_nodes.push [pt, node1, node2] unless grid_nodes.last && grid_nodes.last[0] == pt
	end	
	grid_nodes
end


#GRID: Sample a segment [pt1, pt2] on the grid in X
def grid_sample_segment_in_X(pt1, pt2)
	x1 = [pt1.x, pt2.x].min
	x2 = [pt1.x, pt2.x].max
	return [] if x1 == x2
	
	ls = []
	ix1 = ceil_in_X x1
	ix2 = floor_in_X x2
	ptx = Geom::Point3d.new 0, @ymin, 0
	for ix in ix1..ix2
		ptx.x = @xmin + ix * @dx
		pt = Geom.intersect_line_line [pt1, pt2], [ptx, Y_AXIS]
		next unless pt
		iy1 = floor_in_Y pt.y
		iy2 = ceil_in_Y pt.y
		if iy1 == iy2
			ls.push [pt, pt1.distance(pt), [ix, iy1]]
		else	
			ls.push [pt, pt1.distance(pt), [ix, iy1], [ix, iy2]]
		end	
	end	
	ls
end

#GRID: Sample a segment [pt1, pt2] on the grid in Y
def grid_sample_segment_in_Y(pt1, pt2)
	y1 = [pt1.y, pt2.y].min
	y2 = [pt1.y, pt2.y].max
	return [] if y1 == y2
	
	ls = []
	iy1 = ceil_in_Y y1
	iy2 = floor_in_Y y2
	pty = Geom::Point3d.new @xmin, 0, 0
	for iy in iy1..iy2
		pty.y = @ymin + iy * @dy
		pt = Geom.intersect_line_line [pt1, pt2], [pty, X_AXIS]
		next unless pt
		ix1 = floor_in_X pt.x
		ix2 = ceil_in_X pt.x
		if ix1 == ix2
			ls.push [pt, pt1.distance(pt), [ix1, iy]]
		else	
			ls.push [pt, pt1.distance(pt), [ix1, iy], [ix2, iy]]
		end	
	end	
	ls
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# MARK: Marking on contour footprint on the grid
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#MARK: Mark grid portions in X and Y with contour intervals
def grid_mark_contours
	#Marking the nodes inside the hull
	hull_add_to_contours
	hull_exclude_nodes_outside @hull_pts
	
	#Sampling in X direction
	@key_y = []
	for ix in 0..@nx
		vx = @xmin + ix * @dx
		ls = grid_mark_contours_by_line(@lst_contours, vx, 0)
		ls = ls.collect { |a| [a[0].y, a[1]] }
		compute_key_in_Y ix, ls
	end	 

	#Sampling in Y direction
	@key_x = []
	for iy in 0..@ny
		vy = @ymin + iy * @dy
		ls = grid_mark_contours_by_line(@lst_contours, vy, 1)
		ls = ls.collect { |a| [a[0].x, a[1]] }
		compute_key_in_X iy, ls
	end	
end

#MARK: Calculate the intersection of several contours with a grid line
#   - iv = 0, in X (vertical)
#   - iv = 1, in Y (horizontal)
def grid_mark_contours_by_line(lst_contours, val, iv)
	if iv == 0
		iw = 1
		pt_seg1 = Geom::Point3d.new(val, @ymin, 0)
		pt_seg2 = Geom::Point3d.new(val, @ymax, 0)
	else
		iw = 0
		pt_seg1 = Geom::Point3d.new(@xmin, val, 0)	
		pt_seg2 = Geom::Point3d.new(@xmax, val, 0)
	end
	#bb = Geom::BoundingBox.new.add [pt_seg1, pt_seg2]
	
	#Finding the intersections with the grid line
	linter = []
	lst_contours.each_with_index do |contour, ik|
		###next if contour.bbxy.intersect(bb).empty?
		contour.circles.each do |circle|
			ptcenter, radius, pts, ibeg = circle
			d = ptcenter.distance_to_line([pt_seg1, pt_seg2])
			next unless d < (radius - 0.001)
			for i in 0..pts.length-2
				pt1 = pts[i]
				pt2 = pts[i+1]
				v1 = pt1[iv]
				v2 = pt2[iv]
				next if v1 == v2
				if (v1 <= val && v2 >= val) || (v2 <= val && v1 >= val)
					r = ((v2 - val) / (v2 - v1)).abs
					pt = Geom.linear_combination r, pt1, 1-r, pt2
					linter.push [pt[iw], pt, ik]
				end
			end
		end	
	end	
	
	#Sorting the intersection by abscissa
	linter = linter.sort { |a, b| (a.first == b.first) ? b[1].z <=> a[1].z : a.first <=> b.first }
	
	#Creating the list of intersection. True contours privileged versus hull
	lpt = []
	ptlast = nil
	linter.each do |a|
		pt = a[1]
		ik = a[2]
		if ptlast && (pt[iw] - ptlast[iw]).abs < @tol
			next if @inum_hull && ik == @inum_hull
			lpt[-1] = [pt, ik] if ptlast.z < pt.z || lpt.last[1] == @inum_hull
		else
			lpt.push [pt, ik]
		end	
		ptlast = lpt.last.first
	end	
	lpt
end

#MARK: Calculate the keys in X
def compute_key_in_X(iy, linter)
	t0 = Time.now
	n = linter.length
	
	#No intersection
	if n == 0
		for ix in 0..@nx ; @key_x[iy * (@nx+1) + ix] = [] ; end	
		return
	end
	
	#One or several intersections
	if linter[0][0] == @xmin
		keymin = linter[0][1]
		ibeg = 1
	else
		keymin = nil
		ibeg = 0
	end	
	imin = 0
	for icur in ibeg..n
		a = linter[icur]
		imax, keymax = (a) ? [floor_in_X(a[0]), a[1]] : [@nx, nil]	
		key = contour_key keymin, keymax
		for ix in imin..imax
			@key_x[iy * (@nx+1) + ix] = key
		end
		imin = imax + 1
		keymin = keymax
		break if imax == @nx
	end
end

#MARK: Calculate the keys in Y
def compute_key_in_Y(ix, linter)
	t0 = Time.now
	n = linter.length
	
	#No intersection
	if n == 0
		for iy in 0..@ny ; @key_y[iy * (@nx+1) + ix] = [] ; end	
		return
	end
	
	#One or several intersections
	if linter[0][0] == @ymin
		keymin = linter[0][1]
		ibeg = 1
	else
		keymin = nil
		ibeg = 0
	end	
	imin = 0
	for icur in ibeg..n
		a = linter[icur]
		imax, keymax = (a) ? [floor_in_Y(a[0]), a[1]] : [@ny, nil]	
		key = contour_key keymin, keymax
		for iy in imin..imax
			@key_y[iy * (@nx+1) + ix] = key
		end
		imin = imax + 1
		keymin = keymax
		break if imax == @ny
	end
end

#MARK: Calculate the key combination as a list
def contour_key(a, b)
	if a && b
		key = (a < b) ? [a, b] : [b, a]
	elsif a
		key = [a]
	elsif b	
		key = [b]
	else
		key = []
	end
	key
end

#Return the node with coordintaes ix, iy
def node_from_coord(ix, iy)
	@grid_nodes[iy * (@nx +1 ) + ix]
end

#Compute the floor and ceil integer for an X value
def floor_in_X(x)
	return 0 if (x - @xmin).abs < 0.00001
	return @nx if (x - @xmax).abs < 0.00001
	v = ((x - @xmin) / @dx).floor
	v = @nx if v > @nx
	v = 0 if v < 0
	v
end
def ceil_in_X(x)
	return 0 if (x - @xmin).abs < 0.00001
	return @nx if (x - @xmax).abs < 0.00001
	v = ((x - @xmin) / @dx).ceil
	v = @nx if v > @nx
	v = 0 if v < 0
	v
end

#Compute the floor and ceil integer for an Y value
def floor_in_Y(y)
	return 0 if (y - @ymin).abs < 0.00001
	return @ny if (y - @ymax).abs < 0.00001
	v = ((y - @ymin) / @dy).floor
	v = @ny if v > @ny
	v = 0 if v < 0
	v
end
def ceil_in_Y(y)
	return 0 if (y - @ymin).abs < 0.00001
	return @ny if (y - @ymax).abs < 0.00001
	v = ((y - @ymin) / @dy).ceil
	v = @ny if v > @ny
	v = 0 if v < 0
	v
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# ZONE: Zone determination
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#ZONE: Progressive determination of Zones
def zone_exploration
	@lst_zones = []
	@lst_zone_eqv = []
	for ix in 0..@nx
		for iy in 0..@ny
			k = iy * (@nx+1) + ix	#current node
			node = @grid_nodes[k]
			key_x = @key_x[k]
			key_y = @key_y[k]
			keyprev_x = keyprev_y = nil
			xnd = ynd = nil
			
			#Previous node horizontally at left
			if ix > 0
				xnd = @grid_nodes[k-1]
				keyprev_x = @key_x[k-1]
			end
			
			#Previous node vertically below
			if iy > 0
				ynd = @grid_nodes[k-@nx-1] 
				keyprev_y = @key_y[k-@nx-1]
			end	
			
			#Comparing the nodes keys
			if key_x.object_id == keyprev_x.object_id && key_y.object_id == keyprev_y.object_id
				node.zone = (xnd) ? xnd.zone : ynd.zone
				zone_assign xnd.zone, key_x, key_y
				zone_assign ynd.zone, key_x, key_y
				zone_equivalent xnd.zone, ynd.zone
			elsif key_x.object_id == keyprev_x.object_id
				node.zone = zone_assign xnd.zone, key_y, key_x
			elsif key_y.object_id == keyprev_y.object_id
				node.zone = zone_assign ynd.zone, key_x, key_y
			else
				node.zone = zone_assign nil, key_x, key_y
			end	
		end
	end	
	
	#Transforming the contour vectors into list of contour indices for all zones
	@lst_zones.each do |zone|
		lz = []
		zone.lsk.each_with_index { |a, ik| lz.push ik if a }
		zone.lsk = lz
	end
	
	#Resolving zone equivalence
	lsz = @lst_zone_eqv
	lsz.each do |a|
		zone1, zone2 = a
		ls = zone1.lsk | zone2.lsk
		zone1.lsk = zone2.lsk = ls
	end

	lsz.reverse.each do |a|
		zone1, zone2 = a
		ls = zone1.lsk | zone2.lsk
		zone1.lsk = zone2.lsk = ls
	end

	#Calculating the contours per zone
	@lst_zones.each do |zone|
		zone.lsk.delete @inum_hull if @inum_hull
		zone.contours = zone.lsk.collect { |i| @lst_contours[i] }
	end
	
	#Removing the hull from the list of contours
	if @inum_hull
		@lst_contours.pop
		@lst_contours_loop.pop
	end	
end

#ZONE: Establish equivalence between the zones of two nodes
def zone_equivalent(zone1, zone2)
	idz1 = zone1.object_id
	idz2 = zone2.object_id
	@lst_zone_eqv.push [zone1, zone2] unless idz1 == idz2
end

#ZONE: Create of assign a zone to a node
def zone_assign(zone, key1, key2=nil)
	unless zone
		zone = @struct_Grid_Zone.new
		@lst_zones.push zone
		zone.lsk = []
	end
	lsk = zone.lsk
	key1.each { |i| lsk[i] = true } if key1
	key2.each { |i| lsk[i] = true } if key2
	zone
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# CELL: Cell Management
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#CELL : Create the cell structures
def cell_construction
	t0 = Time.now
	@lst_cells = []
	for ix in 0..@nx-1
		for iy in 0..@ny-1
			cell = @struct_Grid_Cell.new
			cell.even = ((ix + iy).modulo(2) == 0)
			@lst_cells.push cell
			k = iy * (@nx+1) + ix
			cell.nodes = [@grid_nodes[k], @grid_nodes[k+1], @grid_nodes[k + @nx + 2], @grid_nodes[k + @nx + 1]]
			cell.nodes.each do |node|
				node.cells = [] unless node.cells
				node.cells.push cell
			end	
		end
	end	
end

#CELL: Compute the geometric parameters of a cell
def cell_compute(cell)
	nodes = cell.nodes.find_all { |node| node.inside_hull && node.pt }
	ptsxy = cell.ptsxy = nodes.collect { |node| node.ptxy }	
	return false if nodes.length < 3
	cell.polygon = nodes.collect { |node| node.pt }
	poly = cell.polygon
	lines = [poly[0], poly[1], poly[1], poly[2], poly[2]]
	if poly.length == 3
		lines.push poly[0]
	else
		lines.push poly[3], poly[3], poly[0]
	end	
	cell.lines = lines
	
	cell.inside_hull = true
	on_hull = nodes.find { |node| node.inside_hull == 0 }
	
	#Calculating the triangles and quads
	pts = cell.nodes.collect { |node| (node.inside_hull) ? node.pt : nil }	
	triangles = (on_hull) ? cell_split_quad_with_hull_check(cell, pts) : cell_split_quad(cell, pts)
	return false if triangles.empty?
	
	cell.pts = pts.find_all { |pt| pt }
	cell.triangles = triangles
	cell.planes = triangles.collect { |tri| [tri[0], tri[0].vector_to(tri[1]) * tri[0].vector_to(tri[2])] }
	cell.diago = [triangles[0].first, triangles[0].last]
	
	#Calculating the boites
	kboites = nodes.collect { |node| node.boite.key }
	kboites = kboites.uniq
	kboites.each do |key|
		boite = @hsh_boites[key]
		nodes.each { |node| boite.bb.add node.pt }
		boite.cells = [] unless boite.cells
		boite.cells.push cell
	end	
	
	true
end

#CELL: Interpolate the altitude of a point within a cell
def cell_altitude_at_point(cell, ptxy)
	return nil unless cell && cell.triangles
	line = [ptxy, Z_AXIS]
	cell.triangles.each_with_index do |tri, i|
		ptinter = Geom.intersect_line_plane line, cell.planes[i]
		if ptinter && Geom.point_in_polygon_2D(ptinter, tri, true)
			pt_alti = @tr_tot_inv * ptinter
			return pt_alti.z 
		end	
	end
	nil
end

#CELL: Compute the triangles for a cell which is bordered by the hull
def cell_split_quad_with_hull_check(cell, pts)	
	pt0, pt1, pt2, pt3 = pts
	return [] if [pt0, pt1, pt2, pt3].compact.length < 3
	
	#Checking for single triangle
	tri_uniq = nil
	if !pt0
		tri_uniq = [pt1, pt2, pt3]
	elsif !pt1	
		tri_uniq = [pt2, pt3, pt0]
	elsif !pt2	
		tri_uniq = [pt3, pt0, pt1]
	elsif !pt3	
		tri_uniq = [pt0, pt1, pt2]
	end	
	if tri_uniq
		return [tri_uniq] if cell_triangle_within_hull?(tri_uniq)
		return []
	end

	#Quad cell. Divising it into 2 triangles
	normal_a1 = pt0.vector_to(pt1) * pt1.vector_to(pt2)
	normal_b1 = pt2.vector_to(pt3) * pt3.vector_to(pt0)
	angle_1 = normal_a1.angle_between normal_b1
	normal_a2 = pt0.vector_to(pt1) * pt1.vector_to(pt3)
	normal_b2 = pt1.vector_to(pt2) * pt3.vector_to(pt0)
	angle_2 = normal_a2.angle_between normal_b2
	sol1 = [[pt0, pt1, pt2], [pt2, pt3, pt0]]
	sol2 = [[pt3, pt0, pt1], [pt1, pt2, pt3]]
	sol = (angle_1 < angle_2) ? sol1 : sol2
	
	#Checking if the triangles are included in the hull
	sol1.delete_if { |tri| !cell_triangle_within_hull?(tri) }
	sol2.delete_if { |tri| !cell_triangle_within_hull?(tri) }
	return sol if sol1.length == sol2.length
	(sol1.length < sol2.length) ? sol2 : sol1
end

#CELL: Compute the triangles for a cell within the mesh
def cell_split_quad(cell, pts)	
	pt0, pt1, pt2, pt3 = pts
	return [] if [pt0, pt1, pt2, pt3].compact.length < 3
	return [[pt1, pt2, pt3]] unless pt0
	return [[pt2, pt3, pt0]] unless pt1
	return [[pt3, pt0, pt1]] unless pt2
	return [[pt0, pt1, pt2]] unless pt3
	normal_a1 = pt0.vector_to(pt1) * pt1.vector_to(pt2)
	normal_b1 = pt2.vector_to(pt3) * pt3.vector_to(pt0)
	angle_1 = normal_a1.angle_between normal_b1
	normal_a2 = pt0.vector_to(pt1) * pt1.vector_to(pt3)
	normal_b2 = pt1.vector_to(pt2) * pt3.vector_to(pt0)
	angle_2 = normal_a2.angle_between normal_b2
	if angle_1 < angle_2
		tri1 = [pt0, pt1, pt2]
		tri2 = [pt2, pt3, pt0]
	else
		tri1 = [pt3, pt0, pt1]
		tri2 = [pt1, pt2, pt3]
	end
	
	#Checking if the triangles are included in the hull
	[tri1, tri2]
end

#CELL: check if a cell triangle is within the hull
def cell_triangle_within_hull?(pts)
	ptsxy = pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
	bary = G6.straight_barycenter(ptsxy)
	Geom.point_in_polygon_2D(bary, @hull_grid_pts, true)
	#####true
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# HULL: Hull Calculation
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#HULL: Top routine to compute the hull
def hull_calculate
	@box = [0, 1, 3, 2, 0].collect { |i| @bbxy.corner i }
	@hull_proximity = (@xmax - @xmin) * 0.02
	
	#Loading the hull if already calculated
	@hull_pts = attribute_load_hull @group_attr
	return if @hull_pts

	#Determining if all contours are enclosed within a contour
	#Otherwise, try an adjustment of contour extremities to the fitting box
	#Otherwise compute the best-matching concave hull
	unless hull_all_contours_enclosed_in_one
		@lst_contours_open = @lst_contours_open.find_all do |co|
			!@lst_contours_loop.find { |cl| Geom.point_in_polygon_2D(co.ptsxy.first, cl.ptsxy, false) }
		end
		hull_concave unless hull_rectangular
	end
end

#HULL: Determine if all contours are enclosed within a single bigger loop contour
def hull_all_contours_enclosed_in_one
	return false if @lst_contours_loop.empty?
	@lst_contours_loop.sort! { |a, b| b.area <=> a.area }
	biggest = @lst_contours_loop.first
	big_ptsxy = biggest.ptsxy
	
	@lst_contours.each do |contour|
		return false unless contour == biggest || Geom.point_in_polygon_2D(contour.ptsxy[0], big_ptsxy, false)
	end
	@hull_pts = big_ptsxy
	#@hull_big_contour = biggest
	@hull_mode = biggest.inum
	true
end

#HULL: Work first on a rectangular hull and adjust the extremity of contours if very close to the rectangle boundaries
def hull_rectangular
	@hull_sides = []
	@contour_begs = []
	@contour_ends = []
	ls_touched = []
	
	#Marking the extremities of open contours
	@lst_contours.each_with_index do |contour, ik|
		next if contour.loop
		@contour_begs[ik] = true 
		@contour_ends[ik] = true 
	end	
	
	#Compute the touch points on the box rectangle
	for iside in 0..3
		hull_touch_points iside
	end

	#Possibly prolong the extremity if close to the box
	@contour_begs.each_with_index do |val, ik|
		if val && hull_analyze_contour_extremity(ik, 1, 0)
			@contour_begs[ik] = false
			ls_touched[ik] = true
		end
	end	
	@contour_ends.each_with_index do |val, ik|
		if val && hull_analyze_contour_extremity(ik, -2, -1)
			@contour_ends[ik] = false
			ls_touched[ik] = true
		end
	end	
		
	for iside in 0..3
		@hull_sides[iside] = hull_sort @hull_sides[iside], iside
	end

	#Checking if this is enough and no more extremities are left
	if @contour_begs.find { |a| a } || @contour_ends.find { |a| a }
		ls_touched.each_with_index do |a, ik|
			next unless a
			contour = @lst_contours[ik]
			contour.pts = contour.pts_orig.collect { |pt| @tr_tot * pt }
			contour.ptsxy = contour.pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
			contour_compute_boundaries(contour)
		end
		return false
	end	
	
	#Hull can be rectangular
	@hull_pts = @hull_sides[0] + @hull_sides[1][1..-1] + @hull_sides[2][1..-1] + @hull_sides[3][1..-1]
	@hull_mode = :rectangle
	true
end

#HULL: Calculate the concave hull
def hull_concave
	#Calculating the convex hull
	ptsxy = []
	@lst_contours.each { |contour| ptsxy += contour.ptsxy }
	@hull_pts = Traductor::ConvexHull2D.compute ptsxy
	
	#Calculating the extremities of open contours
	ls_on_hull = []
	@contour_anchors = []
	@lst_contours_open.each do |contour|
		[contour.ptsxy.first, contour.ptsxy.last].each_with_index do |pt, ibeg|
			if Geom.point_in_polygon_2D(pt, @hull_pts, false)
				@contour_anchors.push [pt, contour, ibeg]
			else
				ls_on_hull.push pt
			end	
		end	
	end
	
	#Inserting the points on hull into the hull point sequence
	ls_on_hull.each do |pt|
		for i in 0..@hull_pts.length-2
			pt1 = @hull_pts[i]
			pt2 = @hull_pts[i+1]
			next if pt1 == pt || pt2 == pt
			if pt.on_line?([pt1, pt2]) && pt1.vector_to(pt) % pt2.vector_to(pt) < 0
				@hull_pts = @hull_pts[0..i] + [pt] + @hull_pts[i+1..-1]
				break
			end	
		end
	end	
	
	#Performing the adjustments to the hull 
	for i in 0..130
		@vbar.update_time "Pass #{i+1}" if @vbar
		break unless @contour_anchors.compact.length > 0 && hull_concave_pass
	end	
		
	#Hull is concave
	@hull_mode = :concave
end

#HULL: Add the hull as a contour
def hull_add_to_contours
	#Creating a contour from the hull
	if @hull_mode == :concave
		@inum_hull = @lst_contours.length
		@hcontour = Topo_GenContour.new
		@hcontour.inum = @inum_hull
		@hcontour.pts = @hull_pts
		@hcontour.ptsxy = @hull_pts
		@hcontour.loop = true
		contour_compute_boundaries(@hcontour)
	elsif @hull_mode.class == Fixnum
		@hull_big_contour = @lst_contours[@hull_mode]
		return
	else
		return
	end
	
	#Adding the hull cointour temporarily to the list of contours
	@lst_contours_loop.push @hcontour
	@lst_contours.push @hcontour	
end

#HULL: Iteration for concave hull calculation
def hull_concave_pass
	lsections = []
	
	#Calculating the sections
	t0 = Time.now
	nb_anchors = 0
	@contour_anchors.each_with_index do |anchor, ic|
		next unless anchor
		nb_anchors += 1
		pt = anchor.first
		d, ih, pt1, pt2 = G6.proximity_point_curve(pt, @hull_pts)
		hpt1 = @hull_pts[ih-1]
		hpt2 = @hull_pts[ih]
		lsections[ih] = [] unless lsections[ih]
		dd = @hull_pts[ih-1].distance pt2
		lsections[ih].push [dd, ic]
	end
	
	#Modifying the hull
	t0 = Time.now
	hull_new_pts = [] 
	for ih in 1..@hull_pts.length-1
		section = lsections[ih]
		if section
			pts = hull_section_calculate(ih, section)
			hull_new_pts += pts
		else
			hull_new_pts.push @hull_pts[ih-1]
		end	
	end
	hull_new_pts.push @hull_pts.last
	
	#Updating the concave hull points
	@hull_pts = hull_new_pts
	return true if (nb_anchors != @contour_anchors.compact.length)
	
	#No anchor has been resolved - Trying prolongation of extremities if close to hull
	return true if hull_proximity_pass(@hull_proximity)
	
	#No anchor has been resolved - Trying brute force method
	return true if hull_brute_force_pass
	
	#No anchor has been resolved - Trying prolongation of extremities with more distance to hull
	return true if hull_proximity_pass(2 * @hull_proximity)
	
	false
end

#HULL: Brute force method when optimized pass is blocked
def hull_brute_force_pass
	@contour_anchors.each_with_index do |anchor, ic|
		next unless anchor
		pt0 = anchor.first
		for ih in 1..@hull_pts.length-1
			hpt1 = @hull_pts[ih-1]
			hpt2 = @hull_pts[ih]
			d0, = G6.proximity_point_segment(pt0, hpt1, hpt2)
			pt_ok = nil
			if d0 == 0
				next if pt0 == hpt1 || pt0 == hpt2
				pt_ok = pt0
			else
				angle = pt0.vector_to(hpt1).angle_between pt0.vector_to(hpt2)
				pt_ok = pt0 unless angle < 40.degrees || hull_segment_cut_curve?(@hull_pts, hpt1, pt0) || hull_segment_cut_curve?(@hull_pts, pt0, hpt2) ||
					               contour_cut_segment?(hpt1, pt0) || contour_cut_segment?(pt0, hpt2)
			end
			if pt_ok
				@hull_pts[ih, 0] = [pt_ok]
				@contour_anchors[ic] = nil
				return true
			end	
		end
	end	
	false
end

#HULL: Calculate the inclusion of new points in a section of the hull
def hull_section_calculate(ih, section)
	section.sort! { |a, b| a.first <=> b.first }
	
	hpt1 = @hull_pts[ih-1]
	hpt2 = @hull_pts[ih]
	new_pts = [hpt1]
	section.each do |a|
		d0, ic = a
		anchor = @contour_anchors[ic]
		next unless anchor
		pt0 = anchor.first
		pt_ok = nil

		if d0 == 0
			next if pt0 == hpt1 || pt0 == hpt2
			pt_ok = pt0
						
		#Trying to connect the extremity to the hull segment	
		else	
			angle = pt0.vector_to(hpt1).angle_between pt0.vector_to(hpt2)
			pt_ok = pt0 unless angle < 40.degrees || hull_segment_cut_curve?(new_pts, hpt1, pt0) || hull_segment_cut_curve?(new_pts, pt0, hpt2) ||
			                   contour_cut_segment?(hpt1, pt0) || contour_cut_segment?(pt0, hpt2)
		end
		
		#Inserting the point on the hull and removing the extremity from the list
		if pt_ok
			new_pts.push pt_ok
			@contour_anchors[ic] = nil
		end		
	end	

	new_pts
end

#HULL: Check if a a segment [pt1, pt2] cut a curve <pts>
def hull_segment_cut_curve?(pts, pt1, pt2)
	for i in 0..pts.length-2
		seg = [pts[i], pts[i+1]]
		ptinter = G6.intersect_segment_segment seg, [pt1, pt2]
		return true if ptinter && ptinter != pt1 && ptinter != pt2
	end
	false
end

#HULL: Evaluate if contour extremities can be prolonged to reach the hull
def hull_proximity_pass(dmin)
	#Finding the possible prolongation of contour extremities, not too long
	lsinter = []
	@contour_anchors.each_with_index do |anchor, ic|
		next unless anchor
		pt, contour, ibeg = anchor
		for ih in 1..@hull_pts.length-1
			hpt1 = @hull_pts[ih-1]
			hpt2 = @hull_pts[ih]
			if ibeg == 0
				pt0 = contour.ptsxy[1]
				pt1 = contour.ptsxy[0]
			else
				pt0 = contour.ptsxy[-2]
				pt1 = contour.ptsxy[-1]
			end				
			ptinter = hull_oriented_intersection(pt0, pt1, hpt1, hpt2)
			next unless ptinter
			d = pt.distance(ptinter)
			next if d > dmin || contour_cut_segment?(pt, ptinter)		
			lsinter.push [d, ptinter, ic, ih]
		end
	end	

	#Picking the extremity which is the closest
	lsinter = lsinter.sort { |a, b| a.first <=> b.first }
	return hull_proximity_direct_pass if lsinter.empty?

	#Prolonging the contour
	d, ptinter, ic, ih = lsinter.first
	pt, contour, ibeg = anchor = @contour_anchors[ic]
	pts = contour.pts
	if ibeg == 0
		ipos = 0
		line = [pts[0], pts[1]]
	else
		ipos = -1
		line = [pts[-2], pts[-1]]	
	end
	contour.ptsxy[ipos] = ptinter
	pts[ipos] = Geom::intersect_line_line(line, [ptinter, Z_AXIS])
	contour_compute_boundaries(contour)
	@hull_pts[ih, 0] = [ptinter]
	@contour_anchors[ic] = nil	
	true
end

#HULL: Evaluate if contour extremities can be directly linked to hull
def hull_proximity_direct_pass
	#return false
	dmin = @hull_proximity
	
	#Finding the possible prolongation of contour extremities, not too long
	lprox = []
	@contour_anchors.each_with_index do |anchor, ic|
		next unless anchor
		pt, contour, ibeg = anchor
		d, ih, pt1, ptinter = G6.proximity_point_curve(pt, @hull_pts)		
		lprox.push [d, ih, ic, pt, ptinter] if d && d < dmin
	end

	#Sorting by distance
	return false if lprox.empty?
	lprox = lprox.sort { |a, b| a.first <=> b.first }
	
	#Connecting the contour extremity to the hull
	d, ih, ic, pt1, ptinter = lprox.find { |a| !contour_cut_segment?(a[3], a[4]) }
	return false unless ic
	pt, contour, ibeg = anchor = @contour_anchors[ic]
	pts = contour.pts
	if ibeg == 0
		z = pts[0].z
		new_pt = Geom::Point3d.new ptinter.x, ptinter.y, z
		contour.ptsxy.unshift ptinter
		contour.pts.unshift new_pt
	else
		z = pts[-1].z
		new_pt = Geom::Point3d.new ptinter.x, ptinter.y, z
		contour.ptsxy.push ptinter
		contour.pts.push new_pt
	end
	contour_compute_boundaries(contour)
	@hull_pts[ih, 0] = [ptinter]
	@contour_anchors[ic] = nil	
	true
end

def hull_touch_points(iside)
	line = [@box[iside], @box[iside+1]]
	ls_touch = line.clone
	@lst_contours.each_with_index do |contour, ik|
		contour.ptsxy.each do |pt|
			if pt.on_line?(line)
				ls_touch.push pt
				@contour_begs[ik] = false if pt == contour.ptsxy.first
				@contour_ends[ik] = false if pt == contour.ptsxy.last
			end	
		end
	end	
	@hull_sides[iside] = ls_touch
end

def hull_sort(ls, iside)
	case iside
	when 0
		ls.sort { |a, b| a.x <=> b.x }
	when 1
		ls.sort { |a, b| a.y <=> b.y }
	when 2
		ls.sort { |a, b| b.x <=> a.x }
	when 3
		ls.sort { |a, b| b.y <=> a.y }
	end	
end

#HULL: Analyze the contour extremity for a rectangular hull
def hull_analyze_contour_extremity(ik, ibeg, iend)
	contour = @lst_contours[ik]
	ptsxy = contour.ptsxy
	pt0 = ptsxy[ibeg]
	pt1 = ptsxy[iend]
	lsinter = []
	for iside in 0..3
		ptinter = hull_oriented_intersection(pt0, pt1, @box[iside], @box[iside+1])
		next unless ptinter
		d = ptinter.distance(pt1)
		if d < @hull_proximity
			ptsxy[iend] = ptinter
			pts = contour.pts
			pts[iend] = Geom::intersect_line_line([pts[ibeg], pts[iend]], [ptinter, Z_AXIS])
			@hull_sides[iside].push ptinter
			contour_compute_boundaries contour
			return ptinter
		end	
	end	
	nil
end

#HULL: Check if the half-line [pt0, pt1] intersects the side
def hull_oriented_intersection(pt0, pt1, ptside1, ptside2)
	ptinter = Geom.intersect_line_line [pt0, pt1], [ptside1, ptside2]
	return nil unless ptinter
	return ptinter if ptinter == ptside1 || ptinter == ptside2
	return nil unless pt1.vector_to(ptinter) % pt0.vector_to(pt1) > 0
	return nil unless ptinter.vector_to(ptside1) % ptinter.vector_to(ptside2) <= 0
	ptinter
end

#HULL: Compute and mark the nodes which are inside the hull
def hull_exclude_nodes_outside(pts_hull)
	for iy in 0..@ny
		val = @ymin + iy * @dy
		
		#Computing the intersections of the hull and the grid
		linter = []
		for i in 0..pts_hull.length-2
			pt1 = pts_hull[i]
			pt2 = pts_hull[i+1]
			v1 = pt1.y
			v2 = pt2.y
			if v1 != v2 && ((v1 <= val && v2 >= val) || (v2 <= val && v1 >= val))
				r = ((v2 - val) / (v2 - v1)).abs
				linter.push Geom.linear_combination(r, pt1, 1-r, pt2)
			end
		end
		ll = linter.sort { |a, b| a.x <=> b.x }
		linter = []
		ll.each { |pt| linter.push pt unless pt == linter.last }
	
		#Marking the nodes
		flg_inside = true
		inside = []
		k0 = iy * (@nx+1)
		for i in 0..linter.length-2
			if flg_inside
				ibeg = ceil_in_X linter[i].x
				iend = floor_in_X linter[i+1].x
				inside.push [ibeg, iend]
				for ix in ibeg..iend
					@grid_nodes[k0+ix].inside_hull = 1
				end	
			end	
			flg_inside = !flg_inside
		end	
		
	end
	
	#Special treatment when the nodes are on the borders
	for ix in 0..@nx
		node = @grid_nodes[ix]
		node.inside_hull = Geom.point_in_polygon_2D(node.ptxy, @hull_pts, true)
		node = @grid_nodes[@ny * (@nx+1) + ix]
		node.inside_hull = Geom.point_in_polygon_2D(node.ptxy, @hull_pts, true)
	end	
	for iy in 0..@ny
		node = @grid_nodes[iy * (@nx+1) + @nx]
		node.inside_hull = Geom.point_in_polygon_2D(node.ptxy, @hull_pts, true)
		node = @grid_nodes[iy * (@nx+1)]
		node.inside_hull = Geom.point_in_polygon_2D(node.ptxy, @hull_pts, true)
	end	
end

#HULL: Generate a Contour structure for the hull
def hull_make_contour
	pts = @hull_grid_pts
	@hull_contour = hcontour = Topo_GenContour.new
	hcontour.pts_orig = hcontour.pts = hcontour.ptsxy = @hull_grid_pts
	hcontour.inum = @lst_contours.length
	hcontour.loop = true
	contour_compute_boundaries hcontour
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# METHOD: Top Level method for calculation
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

def store_node_friends(node, *args)
	node.node_friends = [] unless node.node_friends
	args.each do |nd|
		node.node_friends.push nd if nd && !node.node_friends.include?(nd)
	end
end

#METHOD: Assign method for nodes determined by at least 2 contours
def method_multi
	@hull_grid_pts = @hull_pts
	@grid_nodes.each do |node|
		next unless node && node.inside_hull
		contours = node.zone.contours
		if contours.length > 1
			method_declare_multi node, contours, 'native'
		else	
			@uncomputed_nodes.push node
			node.contours = contours
		end	
	end	
end

#METHOD: Assign method for nodes determined by only one contour
def method_single(node)
	return if node.type == :multi || node.type == :on_contour
	
	lres_x = method_find_out_contours(node, 0)
	return unless lres_x
	lres_y = method_find_out_contours(node, 1)
	return unless lres_y
	
	contour_node_x, contours_x = lres_x
	contour_node_y, contours_y = lres_y
	
	if contour_node_x != contour_node_y
		contours = []
		contours.push contour_node_x if contour_node_x
		contours.push contour_node_y if contour_node_y
		method_declare_multi node, contours.collect { |ik| @lst_contours[ik] }, "Contours different"
	else	
		contours = (contours_x | contours_y).collect { |ik| @lst_contours[ik] }
		contour_node = (contour_node_x) ? contour_node_x : contour_node_y
		method_declare_single node, @lst_contours[contour_node], contours, "Single"
	end	
end

#METHOD: Find the bordering nodes for an Single node
def method_find_bordering_multi(node, iaxis)
	ix = node.ix
	iy = node.iy

	if iaxis == 0
		#Finding Left node already computed
		node_beg = nil
		for i in 0..ix-1
			j = ix - 1 - i
			node_beg = @grid_nodes[iy * (@nx+1) + j]
			break if !node_beg.inside_hull || node_beg.type == :multi #|| node_beg.type == :on_contour
		end	

		#Finding Right node already computed
		node_end = nil
		for i in ix+1..@nx
			node_end = @grid_nodes[iy * (@nx+1) + i]
			break if !node_end.inside_hull || node_end.type == :multi #|| node_end.type == :on_contour
		end
		
	else
		#Finding Bottom node already computed
		node_beg = nil
		for i in 0..iy-1
			j = iy - 1 - i
			node_beg = @grid_nodes[j * (@nx+1) + ix]
			break if !node_beg.inside_hull || node_beg.type == :multi #|| node_beg.type == :on_contour
		end	

		#Finding Top node already computed
		node_end = nil
		for i in iy+1..@ny
			node_end = @grid_nodes[i * (@nx+1) + ix]
			break if !node_end.inside_hull || node_end.type == :multi #|| node_end.type == :on_contour
		end

	end
	[node_beg, node_end]
end
	
#METHOD: Compute reference and outside contours for a Single node
def method_find_out_contours(node, iaxis)
	ix = node.ix
	iy = node.iy
	ptxy = node.ptxy
	
	#Finding multi nodes on each side
	node_beg, node_end = method_find_bordering_multi node, iaxis

	#Finding the contours intersected on each side
	ls_beg = (node_beg) ? contour_segment_intersection(ptxy, node_beg.ptxy) : nil	
	ls_end = (node_end) ? contour_segment_intersection(ptxy, node_end.ptxy) : nil
	
	node_beg = nil unless node_beg && node_beg.type == :multi
	node_end = nil unless node_end && node_end.type == :multi
	
	#Special case: Node directly connected to a Multi node
	if !ls_beg && node_beg && node_beg.type == :multi
		ptmid = Geom::linear_combination(0.5, ptxy, 0.5, node_beg.ptxy)
		if Geom.point_in_polygon_2D(ptmid, @hull_pts, true)
			method_declare_multi node, node_beg.contours, "FRIBEG [#{node_beg.ix},#{node_beg.iy}]"
			store_node_friends node, node_beg
			#puts "FRIDEN BEG MULTI #{iaxis} ix=#{ix} iy = #{iy}"
			return nil
		end	
	end	

	#Special case: Node directly connected to a Multi node
	if !ls_end && node_end && node_end.type == :multi
		ptmid = Geom::linear_combination(0.5, ptxy, 0.5, node_end.ptxy)
		if Geom.point_in_polygon_2D(ptmid, @hull_pts, true)
			method_declare_multi node, node_end.contours, "FRIEND [#{node_end.ix},#{node_end.iy}]"
			store_node_friends node, node_end
			#puts "FRIDEN END MULTI #{iaxis} ix=#{ix} iy = #{iy}"
			return nil
		end	
	end	
	
	#Special case: Node squizzed between two different contours --> Multi
	if ls_beg && ls_end
		contour_beg, = ls_beg[0]
		contour_end, = ls_end[0]
		if contour_beg != contour_end
			method_declare_multi node, [contour_beg, contour_end], "SAVE MULTI"
			return nil
		end
	end	

	contour_node = (ls_beg) ? ls_beg[0][0].inum : ((ls_end) ? ls_end[0][0].inum : nil)
	contour_node = node.zone.contours[0].inum unless contour_node || node.zone.contours.empty?
	contours = []
	contours |= node_beg.contours.collect { |contour| contour.inum } if node_beg && (!ls_beg || ls_beg.length == 1)
	contours |= node_end.contours.collect { |contour| contour.inum } if node_end && (!ls_end || ls_end.length == 1)
	contours.delete contour_node
	[contour_node, contours]
end
		
#METHOD: Compute the intersection of the hull and the grid and adjust the nodes
def method_hull_border
	#Calculating the intersections in X and in Y
	hull_pts = (@hull_big_contour) ? @hull_big_contour.ptsxy : @hull_pts
	hull_grid_info = grid_sample_contour hull_pts
	
	#Selecting the nodes to be part of the hull
	hsh_node_hull = {}
	@hull_nodes = []
	fac = 4
	hull_grid_info.each do |a|
		pt, node1, node2 = a
		ptxy = Geom::Point3d.new pt.x, pt.y, 0
		if node2
			d1 = ptxy.distance(node1.ptxy)
			d1 = fac * d1 if node1.inside_hull
			d2 = ptxy.distance(node2.ptxy)
			d2 = fac * d2 if node2.inside_hull
			node = (d1 < d2) ? node1 : node2
			ls = hsh_node_hull[node.object_id]
			ls = hsh_node_hull[node.object_id] = [node] unless ls
			ls.push [ptxy, pt]
		else
			node = node1
		end
		@hull_nodes.push node unless @hull_nodes.last == node
	end	
	
	#Moving the nodes to fit the hull
	hsh_node_hull.each do |node_id, info|
		node = info[0]
		ls = info[1..-1].collect { |a| [a[0], a[1], a[1].distance(node.ptxy) * ((node.inside_hull) ? fac : 1)] }
		ls = ls.sort { |a, b| a[2] <=> b[2] }
		node.ptxy = ls[0][0]
		node.shiftedX = (@xmin + node.ix * @dx != node.ptxy.x)
		node.shiftedY = (@ymin + node.iy * @dy != node.ptxy.y)
		if @hull_big_contour
			pt = contour_3d_from_2d node.ptxy, @hull_big_contour
			method_declare_on_contour node, pt, @hull_big_contour if pt
		end			
	end
	
	#Declaring the hull nodes inside hull
	@hull_nodes.push @hull_nodes.first if @hull_nodes[-1] != @hull_nodes[0]
	@hull_nodes.each { |node| node.inside_hull = 0 }
	@hull_grid_pts = @hull_nodes.collect { |node| node.ptxy }
		
	#Hull if a contour - Job done
	return if @hull_big_contour
	
	#Make the hull a pseudo contour
	hull_make_contour
	
	#Assigning a method to the remaining nodes which are multi by vicinity
	n = @hull_nodes.length
	@hull_nodes.each_with_index do |node, i|
		vbar_mini_progression(n, i, 40, 20)		
		next if node.type
		if method_hull_vicinity(node)
			@uncomputed_nodes.delete node
		else
			@uncomputed_nodes.push node unless @uncomputed_nodes.include?(node)
		end	
	end
end

#METHOD: Recover multi nodes by neighbours
def method_recup_node_by_neighbours(node)
	lsn = node.direct_neighbours
	return false unless lsn
	status = false
	if node.type == :multi
		lsn.each do |nd|
			unless nd.type
				method_declare_multi nd, node.contours, "RECUP A [#{node.ix},#{node.iy}]"
				status = true
				@uncomputed_nodes.delete nd
			end	
		end	
		node.direct_neighbours = nil
	elsif !node.type
		nd = lsn.find { |nd| nd.type == :multi }
		if nd
			method_declare_multi node, nd.contours, "RECUP B [#{nd.ix},#{nd.iy}]"
			status = true
			@uncomputed_nodes.delete node
		end
	end	
	status
end

#METHOD: Check if the node0 is connected directly to other multi nodes
def method_hull_vicinity(node0)
	ix0 = node0.ix
	iy0 = node0.iy
	ptxy0 = node0.ptxy
	
	pass = 0
	ls_neighbours = [[ix0-1, iy0], [ix0+1, iy0], [ix0, iy0-1], [ix0, iy0+1]]
	ls_neighbours += [[ix0-1, iy0-1], [ix0+1, iy0-1], [ix0+1, iy0+1], [ix0-1, iy0+1]]
	node_neighbours = []
	ls_neighbours.each do |a|
		ix, iy = a
		next if ix < 0 || ix > @nx || iy < 0 || iy > @ny
		node = node_from_coord(ix, iy)
		node_neighbours.push [node, node.ptxy.distance(ptxy0)] if node.inside_hull && node.contours
	end
	node_neighbours = node_neighbours.sort { |a, b| a[1] <=> b[1] }

	lsk = []	
	node_neighbours.each do |a|
		node, = a
		ptxy = node.ptxy
		
		#Check if segment is within hull
		ptmid = Geom.linear_combination 0.5, ptxy0, 0.5, ptxy
		next unless Geom.point_in_polygon_2D(ptmid, @hull_pts, true)
		
		#Checking if there is an intersection by a contour between the node and the neighbors
		ls = contour_segment_intersection(ptxy0, ptxy, @lst_contours)
		contour, ptinter_xy, ptinter_3d, d = (ls) ? ls[0] : nil
		
		#Node is in direct connection with a resolved node
		if !contour
			if node.type == :multi 
				method_declare_multi node0, node.contours, "VICI from [#{node.ix},#{node.iy}]"
				store_node_friends node0, node
				return true
			else
				node0.direct_neighbours = [] unless node0.direct_neighbours
				node0.direct_neighbours.push node unless node0.direct_neighbours.include?(node)
			end
			
		#Node is actually exactly on a contour
		elsif d < 0.5 * @proximity_nk
			method_declare_on_contour node0, ptinter_3d, contour
			return true
			
		else
			lsk.push contour.inum unless lsk.include?(contour.inum)
		end	
	end	
	
	contours = lsk.collect { |ik| @lst_contours[ik] }
	if lsk.length > 1
		method_declare_multi node0, contours, "HULL MULTI #{lsk.length}"
	elsif lsk.length == 1
		node0.contours = contours
	end
	false
end
	
#METHOD: Register a node exactly on a contour
def method_declare_on_contour(node, pt, contour, reason=nil)
	node.type = :on_contour
	node.pt = Geom::Point3d.new node.ptxy.x, node.ptxy.y, pt.z
	node.contours = [contour]
	@nodes_on_contour.push node
	if reason
		node.reasons = [] unless node.reasons
		node.reasons.push reason
	end	
end

#METHOD: Register a node detremined by multiple contours
def method_declare_multi(node, contours=nil, reason=nil)
	node.type = :multi
	node.contours = (contours) ? contours : node.zone.contours
	@nodes_multi.push node
	if reason
		node.reasons = [] unless node.reasons
		node.reasons.push reason
	end	
end

#METHOD: Register a node determined by a single contour with 2 boundaries
def method_declare_single(node, ref_contour, contours, reason=nil)
	return unless node.inside_hull
	return if node.type == :multi || node.type == :on_contour
	node.type = :single
	node.contours = contours
	node.ref_contour = ref_contour
	ref_contour.single = true
	@option_hilltop_possible = true
	@nodes_single.push node unless @nodes_single.include?(node)
	if reason
		node.reasons = [] unless node.reasons
		node.reasons.push reason
	end	
end
		
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# ALTITUDE: Interpolation and Extrapolation of altitude
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#ALTITUDE: calculate altitude for nodes detremined by multiple contours
def altitude_multi(node)
	node.pt = altitude_at_point node.ptxy, node.ref_contour, node.contours
end

#ALTITUDE: calculate altitude for nodes determined by single contours with at least containment
def altitude_single(node)
	node.pt = altitude_at_point node.ptxy, node.ref_contour, node.contours
end

#ALTITUDE: Compute the altitude in one point based on given contours (interpolation and extrapolation)
def altitude_at_point(ptxy, ref_contour, contours)
	#Calculating the paths
	lpath_d = []
	lpath_z = []
	lpath_k = []
	
	#Extrapolation with a Ref contour for Single nodes
	if ref_contour
		d, iseg, pt, ptc = G6.proximity_point_curve_by_circle(ptxy, ref_contour.circles)
		ptz = Geom.intersect_line_line [ptc, Z_AXIS], ref_contour.pts[iseg-1..iseg]
		ref_z = altitude_reference_single ptxy, ref_contour, ptz
		return Geom::Point3d.new(ptxy.x, ptxy.y, ref_z) if ref_contour.option_hilltop == :flat
		lpath_k.push ref_contour.option_cliff
		lpath_z.push ref_z
		lpath_d.push d
	end
	
	#Other contours
	contours.each do |contour|
		pts = contour.pts
		d, iseg, pt, ptc = G6.proximity_point_curve_by_circle(ptxy, contour.circles)
		ptz = Geom.intersect_line_line [ptc, Z_AXIS], pts[iseg-1..iseg]
		lpath_k.push contour.option_cliff
		lpath_d.push d
		lpath_z.push ptz.z
	end	
		
	#Correction for cliff
	#lpath_d, lpath_z = altitude_cliff_correction(lpath_k, lpath_d, lpath_z)
	
	#Correction for out-contours for single nodes
	lpath_z = lpath_z.collect { |z| 2 * ref_z - z } if ref_contour
	
	#Calculate altitude by interpolation
	z = G6.multi_average lpath_z, lpath_d
	Geom::Point3d.new(ptxy.x, ptxy.y, z)
end

#ALTITUDE: compute the reference altitude for a node Single based on a cross
def altitude_reference_single(ptxy, contour, ptinter)
	ptzxy = Geom::Point3d.new ptinter.x, ptinter.y, 0
	return ptinter.z if contour.zmin == contour.zavg || ptxy == ptzxy
	
	#Points of the cross
	vecl = ptzxy.vector_to ptxy
	vecp = Z_AXIS * vecl
	pt2 = ptxy.offset(vecl, @big_length)
	pt3 = ptxy.offset(vecp, @big_length)
	pt4 = ptxy.offset(vecp, -@big_length)

	#Averaging the altitudes
	ldist = [ptzxy.distance(ptxy)]
	lz = [ptinter.z]
	[pt2, pt3, pt4].each do |pt|
		ls = contour_segment_intersection(ptxy, pt, [contour])
		next unless ls
		contour1, ptxy1, pt3d, d = ls.first
		if contour1
			ldist.push d
			lz.push pt3d.z
		end	
	end	
	G6.multi_average lz, ldist
end

def altitude_cliff_correction(lsk, lsd, lsz)
	n = lsd.length-1
	for i in 0..n
		fac = lsk[i]
		next if !fac || fac == 0
		fac = (fac == 100) ? 1000.0 : 100.0 / (100 - fac)
		z = lsz[i]
		lower = []
		bigger = []
		for j in 0..n
			next if j == i
			(lsz[j] < z) ? lower.push(j) : bigger.push(j)
		end
		lower.each { |k| lsd[k] /= fac }
		#bigger.each { |k| lsd[k] *= fac }
	end
	[lsd, lsz]
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# GEOMETRY: Generation of SU faces
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#GEOMETRY: Execute the generation of Terrain geometry
def geometry_execute(suops, gr_attr)
	@vgbar = geometry_initial_step
	@vgbar.start
	suops.start_execution { geometry_step_exec suops }
	gr_attr.erase! if gr_attr && gr_attr.valid?
end

#GEOMETRY: Main State Automat function to build the geometry
def geometry_step_exec(suops)
	while(action, *param = suops.current_step) != nil
	
		case action
		when :_init
			@top_group.erase! if @top_group && @top_group.valid?
			@top_group_wall = @top_group_contours = @top_group_map = nil
			@top_group = @model.active_entities.add_group
			@top_entities = @top_group.entities
			next_step = [:mesh, 0, 0]
		
		when :mesh
			return if suops.yield?
			istep, irange = param
			irange = geometry_fill_mesh(@top_entities, irange)
			next_step = (irange) ? [action, istep+1, irange] : [:commit_mesh, 0]	
		
		when :commit_mesh
			return if suops.yield?
			geometry_commit_mesh @top_entities
			next_step = [:diagonal]
					
		when :diagonal
			return if suops.yield?
			geometry_mark_diagonal @top_entities
			next_step = [:wall]
					
		when :wall
			return if suops.yield?
			geometry_wall if @hull_nodes
			next_step = [:contours]
			
		when :contours
			return if suops.yield?
			geometry_contours
			next_step = [:map]
			
		when :map
			return if suops.yield?
			geometry_map
			next_step = [:finish]

		when :finish	
			suops.abort_operation if @top_entities.length == 0
			attribute_write @top_group
			gr_attr = @model.active_entities.grep(Sketchup::Group).find { |e| e.entityID == @entity_attr }
			gr_attr.erase! if gr_attr
			next_step = nil
		end	
		
		break if suops.next_step(*next_step)
	end
end

#GEOMETRY: Notification of the Termination of the geometry
def geometry_terminate
	@vgbar.stop
	@vgbar = nil
	@istep = nil
end

#GEOMETRY: Initialize the generation
def geometry_initial_step
	#Forming buckets of cell for geometry generation
	ndiv = 2500
	ncells = @lst_cells_geom.length
	nd = ncells / ndiv
	@ranges_geom = []
	for i in 0..nd
		ibeg = i * ndiv
		iend = (i == nd) ? ncells-1 : ibeg + ndiv - 1
		@ranges_geom.push ibeg..iend
	end
	@nsteps_geom = @ranges_geom.length
	@nsteps = @nsteps_geom + 4
	
	#Creating the mesh
	@terrain_mesh = Geom::PolygonMesh.new	
	
	#Creating the visual bar
	hsh = { :delay => 0.5, :style_color => :bluegreen }
	@vgbar = Traductor::VisualProgressBar.new T6[:TIT_GeneratingGeometry], hsh
	@vgbar_step = 1.0 / @nsteps
	@istep = 0
	@txt_vgbar_fill_mesh = T6[:VGBAR_FillMesh]
	@vgbar
end

#GEOMETRY: Filling the mesh for a range of cells
def geometry_fill_mesh(entities, irange)
	range = @ranges_geom[irange]
	return nil unless range
	
	geometry_progression
	ibeg = range.begin
	n = range.end - range.begin + 1
	for i in range
		geometry_mini_progression(n, i-ibeg, 1500, 300)
		cell = @lst_cells_geom[i]
		cell.triangles.each do |tri|
			@terrain_mesh.add_polygon tri.collect { |pt| @tr_tot_inv * pt }
		end
	end
	
	(irange == @ranges_geom.length-1) ? nil : irange + 1
end

#GEOMETRY: Filling the mesh for a range of cells
def geometry_commit_mesh(entities)
	geometry_progression
	entities.fill_from_mesh @terrain_mesh, true, 12
	use_color = MYDEFPARAM[:TPS_UseColorTerrain]
	if use_color
		color =  MYDEFPARAM[:TPS_ColorTerrain]
		entities.grep(Sketchup::Face).each { |f| f.material = color }
	end	
end

#GEOMETRY: Filling the mesh for a range of cells
def geometry_mark_diagonal(entities)
	geometry_progression
	faces = entities.grep(Sketchup::Face)
	faces.each { |face| face.edges[2].casts_shadows = false }
end

#Generate the geometry for the 3D Contours
def geometry_contours
	geometry_progression

	#Deleting the group of contours if not requested
	unless @hsh_options[:geometry_contour]
		@top_group_contours.erase! if @top_group_contours && @top_group_contours.valid?
		return
	end
	
	#Creating the contours geometry
	@top_group_contours = @top_entities.add_group
	gent = @top_group_contours.entities
	
	@lst_contours.each do |contour|
		pts = contour.pts.collect { |pt| @tr_tot_inv * pt }
		gent.add_curve pts
	end	
	
	use_color = MYDEFPARAM[:TPS_UseColorIso]
	if use_color
		color =  MYDEFPARAM[:TPS_ColorIso]
		gent.grep(Sketchup::Edge).each { |e| e.material = color }
	end		
end

#Generate the geometry for the 2D Map
def geometry_map
	geometry_progression

	#Level of the map
	zlevel = (@tool.registry_info_get[:geometry_map_top]) ? @zmax + (@zmax - @zmin) * 0.1 : @zmin
	with_label = @tool.registry_info_get[:geometry_label]
	
	#Deleting the group of contours if not requested
	unless @hsh_options[:geometry_map]
		@top_group_map.erase! if @top_group_map && @top_group_map.valid?
		return
	end
	
	#Creating the contours geometry
	@top_group_map = @top_entities.add_group
	gent = @top_group_map.entities
	
	@lst_contours.each do |contour|
		pts = contour.ptsxy.collect { |pt| @tr_tot_inv * Geom::Point3d.new(pt.x, pt.y, zlevel) }
		gent.add_curve pts
	end	
	
	lpt = @hull_grid_pts.collect { |pt| @tr_tot_inv * Geom::Point3d.new(pt.x, pt.y, zlevel) }
	grp = gent.add_group
	edges = grp.entities.add_curve lpt
	color = @color_hull
	edges.each { |e| e.material = color }
	
	lpt = [[@xmin, @ymin, zlevel], [@xmax, @ymin, zlevel], [@xmax, @ymax, zlevel], [@xmin, @ymax, zlevel]].collect { |a| @tr_tot_inv * Geom::Point3d.new(*a) }
	grp = gent.add_group
	face = grp.entities.add_face lpt
	color = @color_geometry_map
	face.material = face.back_material = color
	
	geometry_label
end

#GEOMETRY: Create the height labels
def geometry_label
	return unless @tool.registry_info_get[:geometry_label]
	
	zlevel = (@tool.registry_info_get[:geometry_map_top]) ? @zmax + (@zmax - @zmin) * 0.1 : @zmin
	
	#Size of label
	fac = @tool.registry_info_get[:geometry_label_factor]
	fac = 1.0 unless fac
	size_label = fac * (@xmax - @xmin + @ymax - @ymin) / 200

	#Creating the group
	@top_group_label = @top_group_map.entities.add_group
	label_entities = @top_group_label.entities
	
	#Filter
	alt_ref = @tool.registry_info_get[:alt_ref]
	alt_ref = 0 unless alt_ref
	filter = nil
	if @tool.registry_info_get[:geometry_label_filter_on]
		filter = geometry_option_label_filter_get
		filter = nil if filter == 0
	end	
	
	#Creating the labels
	@lst_contours.each do |contour|
		pts = contour.ptsxy.collect { |pt| @tr_tot_inv * Geom::Point3d.new(pt.x, pt.y, zlevel) }
		alt = (contour.altitude - alt_ref).abs
		next if filter && (((alt / filter).round - alt / filter).abs).abs > 0.1
		geometry_label_contour(label_entities, pts, size_label, contour.altitude)
	end		
end

#GEOMETRY: Generate the altitude label for the contour
def geometry_label_contour(entities, pts, size, altitude)
	#Creating the 3D text
	font = "Times New Roman"
	g = entities.add_group
	text = Sketchup.format_length altitude
	#text = text.split(//).join ' '
	status = g.entities.add_3d_text(text, TextAlignCenter, font, false, false, size)
	return unless status
	g.material = 'black'
	bb = g.bounds
	w = bb.width
	
	#Calculating the middle of the contour
	n = pts.length - 2
	totlen = 0
	len = []
	for i in 0..n
		d = pts[i].distance pts[i+1]
		totlen += d
		len[i] = d
	end
	
	lenmid = totlen * 0.5
	i = 0
	d = 0
	for i in 0..n
		d += len[i]
		break if d > lenmid
	end
	
	#Adjusting the segment if not long enough
	scale = 1
	if len[i] < w && i > 0 && i < n
		i2 = nil
		for j in i+1..n
			if len[j] >= w
				i2 = j
				break
			end
		end	
		unless i2
			for j in 0..i-1
				k = i-1-j
				if len[k] >= w
					i2 = k
					break
				end
			end
		end	
		if i2 
			i = i2
		else	
			scale = [0.75, len[i] * 0.9 / w].max
		end
	end	
	
	#Calculating Rotation
	vector = pts[i].vector_to pts[i+1]
	vector = vector.reverse if vector % X_AXIS < 0
	angle = X_AXIS.angle_between vector
	if vector.parallel?(X_AXIS)
		angle == 0
	elsif (X_AXIS * vector) % Z_AXIS < 0	
		angle = -angle 
	end	
	
	#Segment vector and anchor point
	ptmid = Geom.linear_combination 0.5, pts[i], 0.5, pts[i+1]
	ptmid = ptmid.offset vector, -w * 0.5 * scale
	ptmid.z += 1
	
	#Calculating transformations
	ts = Geom::Transformation.scaling scale
	tt = Geom::Transformation.translation ORIGIN.vector_to(ptmid)
	trot = Geom::Transformation.rotation ptmid, Z_AXIS, angle
	
	#Transforming the text
	entities.transform_entities trot * tt * ts, [g]
end

#GEOMETRY: Generate the geometry for the wall
def geometry_wall
	geometry_progression
	return unless @hull_nodes
	
	#Deleting the wall if not requested
	unless @hsh_options[:geometry_wall]
		@top_group_wall.erase! if @top_group_wall && @top_group_wall.valid?
		return
	end
	
	@top_group_wall = @top_entities.add_group
	gent = @top_group_wall.entities

	#Checking nodes for duplication
	lsnodes_pt = []
	@hull_nodes.each do |node|
		ptnode = node.pt
		lsnodes_pt.push ptnode unless lsnodes_pt.find { |pt| ptnode == pt }
	end
	lsnodes_pt.push lsnodes_pt.first
	
	#Generating the skirt
	mesh = Geom::PolygonMesh.new
	zmin = (@zmin > 0) ? 0 : @zmin
	for i in 0..lsnodes_pt.length-2
		pt3 = lsnodes_pt[i]
		pt2 = lsnodes_pt[i+1]
		next unless pt2 && pt3
		next if pt2 == pt3
		pt0 = Geom::Point3d.new(pt3.x, pt3.y, zmin)
		pt1 = Geom::Point3d.new(pt2.x, pt2.y, zmin)
		next if pt0 == pt3 && pt1 == pt2
		lspt = []
		[pt0, pt1, pt2, pt3].each { |pt| lspt.push pt unless pt == lspt.last || pt == lspt.first }
		next if lspt.length < 3
		mesh.add_polygon lspt.collect { |pt| @tr_tot_inv * pt }
	end
	
	#Creating the faces
	use_color = MYDEFPARAM[:TPS_UseColorSkirt]
	gent.fill_from_mesh mesh, false, 0
	if use_color
		color = MYDEFPARAM[:TPS_ColorSkirt]
		gent.grep(Sketchup::Face).each { |f| f.material = color }
	end	
	
	#Making edges soft when angle is not too high
	edges = gent.find_all { |e| e.instance_of?(Sketchup::Edge) && e.faces.length == 2 }
	edges.each do |e|
		face1, face2 = e.faces
		angle = face1.normal.angle_between face2.normal
		e.soft = true if angle < 20.degrees
	end	
end

#GEOMETRY: Main progression of the visual bar
def geometry_progression
	return unless @vgbar
	d = @istep - @nsteps_geom
	ratio = 1.0 * @istep / @nsteps 
	case d
	when 0
		msg = T6[:VGBAR_GenMesh]
	when 1
		msg = T6[:VGBAR_MarkDiago]
	when 2
		msg = T6[:VGBAR_GenWall]
	when 3
		msg = T6[:VGBAR_GenContours]
	when 4
		msg = T6[:VGBAR_GenMap]
	else
		msg = @txt_vgbar_fill_mesh
	end	
	@vgbar.progression ratio, msg
	@istep += 1
end

#GEOMETRY: Mini progression of the visual bar
def geometry_mini_progression(n, i, min, slice=100)
	return unless @vgbar
	return if n < min || n < 2 * slice 
	@vgbar.mini_progression(i * 1.0 / n, @vgbar_step) if i.modulo(slice) == 0
end

#GEOMETRY: Toggle option for Wall generation
def geometry_option_wall_toggle
	@hsh_options[:geometry_wall] = !@hsh_options[:geometry_wall]
	geometry_wall
end

#GEOMETRY: Toggle option for Contour generation
def geometry_option_contour_toggle
	@hsh_options[:geometry_contour] = !@hsh_options[:geometry_contour]
	geometry_contours
end

#GEOMETRY: Toggle option for 2D Map generation
def geometry_option_map_toggle
	@hsh_options[:geometry_map] = !@hsh_options[:geometry_map]
	geometry_map
end

#GEOMETRY: Toggle option for 2D Map generation
def geometry_option_label?
	@tool.registry_info_get[:geometry_label]
end

#GEOMETRY: Set the factor for the size of labels
def geometry_option_label_size_get
	fac = @tool.registry_info_get[:geometry_label_factor]
	(fac) ? fac : 1.0
end
def geometry_option_label_size_set(fac)
	@tool.registry_info_set :geometry_label_factor, fac
	geometry_recreate_labels
end

#GEOMETRY: Toggle the option for showing / hiding labels
def geometry_option_label_toggle
	@tool.registry_info_toggle :geometry_label
	geometry_recreate_labels
end

#GEOMETRY: Toggle the position of the contour map
def geometry_option_map_position_toggle
	@tool.registry_info_toggle :geometry_map_top
	if @hsh_options[:geometry_map]
		@top_group_map.erase! if @top_group_map && @top_group_map.valid?
		geometry_map
	end	
end

def geometry_option_label_filter_toggle
	@tool.registry_info_toggle :geometry_label_filter_on
	geometry_recreate_labels
end

def geometry_option_label_filter_get
	val = @tool.registry_info_get[:geometry_label_filter]
	val = @tool.registry_info_get[:alt_incr] unless val
	val = 1.m unless val
	val.inch
end

def geometry_option_label_filter_set(val)
	@tool.registry_info_set :geometry_label_filter, val.abs
	@tool.registry_info_set :geometry_label_filter_on, true
	geometry_recreate_labels
end

def geometry_recreate_labels
	if @hsh_options[:geometry_map]
		@top_group_label.erase! if @top_group_label && @top_group_label.valid?
		geometry_label
	end		
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# MOUSE: Manage mouse location and click
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#MOUSE: Check the potential highlight of an element of the preview based on the mouse position
def mouse_on_move(flags, x, y, view)
	return false unless @dessin_info && @dessin_info.done
	ray = view.pickray x, y
	eye = ray[0]
	@hi_cell = nil
	@hi_node = nil
	@hi_contour = nil
	@current_altitude = nil
	lres = []
	
	#Checking if the mouse is in the terrain
	res = mouse_in_terrain(x, y, ray)
	@zoom_target = res[2]
	lres.push [eye.distance(@zoom_target), res] if res[0]
	
	#Checking if the mouse is in the map
	unless camera_in_situ? 
		res = mouse_in_map(ray)
		@zoom_target = res[2]
		lres.push [eye.distance(@zoom_target), res] if res[0] || res[1]
	end	
	
	#Checking if mouse is in the wall
	res = mouse_in_wall(x, y, ray)
	@zoom_target = res[2]
	lres.push [eye.distance(@zoom_target), res] if res[0]
	
	#Neither in terrain nor map
	return false if lres.empty?
	
	#Taking the closest one from camera eye
	lres = lres.sort { |a, b| a.first <=> b.first }
	@hi_cell, @hi_node, @zoom_target, @current_altitude, where = lres[0][1]
	
	#Finding the selected contour if any
	if where == :map
		@hi_contour = mouse_contour_in_map(@zoom_target)
	elsif where == :terrain && @hi_cell
		@hi_contour = mouse_contour_in_terrain(eye, @zoom_target)
	end
	
	(@hi_cell || @hi_node || @hi_contour) ? true : false
end

#MOUSE: Check if mouse location is within the Terrain in 3D
def mouse_in_terrain(x, y, ray)
	t = (camera_in_situ?) ? @tr_tot : @tr_dessin_inv
	tinv = t.inverse
	
	#Transform the ray to normalized coordinates
	eye, vray = ray
	ray = [t * eye, t * eye.offset(vray, 10)]
	
	mouse_target = eye.offset vray, eye.distance(t * @bb_3d.center)
	@selected_boites = []
	ptxy = Geom::Point3d.new x, y, 0
	
	@lst_boites.each do |boite|
		@selected_boites.push boite if boite && boite_intersect_ray?(@view, boite, ptxy)
	end	
	return [nil, nil, mouse_target, :terrain] if @selected_boites.empty?

	cells = []
	@selected_boites.each do |boite|
		cells += boite.cells if boite.cells
	end	
	
	#Finding the cell
	mouse_cell = nil
	dmin = nil
	altitude = nil
	cells.each do |cell|
		d, ptinter = mouse_cell_within_ray(cell, ray, dmin)
		if d && (!dmin || d < dmin)
			dmin = d
			mouse_cell = cell
			mouse_target = tinv * ptinter
			altitude = ptinter.z
		end
	end	
	
	#Finding the node
	dmin = 0.2 * (@dx + @dy)
	ptmouse = t * mouse_target
	mouse_node = mouse_cell.nodes.find { |node| node.pt && node.pt.distance(ptmouse) < dmin } if mouse_cell
	
	[mouse_cell, mouse_node, mouse_target, altitude, :terrain]
end

#MOUSE: Check if mouse location is within the Terrain in 3D
def mouse_in_wall(x, y, ray)
	#Transform the ray to normalized coordinates
	t = (camera_in_situ?) ? @tr_tot : @tr_dessin_inv
	tinv = t.inverse	
	eye, vray = ray
	ray = [t * eye, t * eye.offset(vray, 10)]
	
	mouse_target = eye.offset vray, eye.distance(t * @bb_3d.center)
	
	selected_boites = []
	ptxy = Geom::Point3d.new x, y, 0
	@wall_boites.each do |boite|
		selected_boites.push boite if boite_intersect_ray?(@view, boite, ptxy)
	end	
	
	panels = []
	selected_boites.each do |boite|
		panels += boite.panels
	end	
	
	#Finding the panel
	mouse_panel = nil
	dmin = nil
	panels.each do |panel|
		d, ptinter = mouse_panel_within_ray(panel, ray, dmin)
		if d && (!dmin || d < dmin)
			dmin = d
			mouse_panel = panel
			mouse_target = tinv * ptinter
		end
	end	
	
	[nil, nil, mouse_target, :wall]
end

def mouse_cell_within_ray(cell, ray, dmin)
	return nil unless cell && cell.triangles
	cell.triangles.each_with_index do |tri, i|
		ptinter = Geom.intersect_line_plane ray, cell.planes[i]
		next unless ptinter
		d = ray[0].distance ptinter
		next if dmin && d >= dmin	
		return [d, ptinter] if Geom.point_in_polygon_2D(ptinter, tri, true)
	end	
	nil
end

def mouse_panel_within_ray(panel, ray, dmin)
	pt0 = panel[0]
	vec1 = pt0.vector_to(panel[1])
	ptother = (pt0 != panel[3]) ? panel[3] : panel[2]
	normal = vec1 * pt0.vector_to(ptother)
	ptinter = Geom.intersect_line_plane ray, [panel[0], normal]
	return nil unless ptinter
	d = ray[0].distance ptinter
	return nil if dmin && d >= dmin	
	return [d, ptinter] if Geom.point_in_polygon_2D(ptinter, panel, true)
	nil
end

#MOUSE: Check if mouse location is within the Map
def mouse_in_map(ray)
	ptinter = Geom.intersect_line_plane ray, [@tr_bello * ORIGIN, Z_AXIS]
	return [nil, nil, ptinter, :map] unless ptinter && @bb_map.contains?(ptinter)
	pt =  @tr_bello_inv * ptinter
	x = pt.x
	y = pt.y
	pt.z = 0
	return [nil, nil, ptinter, :map] if x < @xmin || x > @xmax || y < @ymin || y > @ymax

	#Identifying a close node
	dx = (x - @xmin) / @dx
	dy = (y - @ymin) / @dy
	ix0 = dx.round
	iy0 = dy.round
	
	ls_neighbours = [[ix0, iy0], [ix0-1, iy0], [ix0+1, iy0], [ix0, iy0-1], [ix0, iy0+1], 
	                 [ix0-1, iy0-1], [ix0+1, iy0-1], [ix0+1, iy0+1], [ix0-1, iy0+1]]

	mouse_node = nil
	dmin = 0.15 * (@dx + @dy)
	ls_neighbours.each do |a|
		ix, iy = a
		next if ix < 0 || ix > @nx || iy < 0 || iy > @ny
		node = @grid_nodes[iy * (@nx+1) + ix]
		d = node.ptxy.distance(pt)
		if d < dmin
			mouse_node = node
			dmin = d
		end
	end
	
	#Identifying the cell if any
	altitude = nil
	mouse_cell = nil
	node0 = @grid_nodes[iy0 * (@nx+1) + ix0]
	node0.cells.each do |cell|
		next unless cell.triangles
		if Geom.point_in_polygon_2D(pt, cell.ptsxy, true)
			mouse_cell = cell
			altitude = cell_altitude_at_point(cell, pt)
			break
		end	
	end
	[mouse_cell, mouse_node, ptinter, altitude, :map]
end
	
#MOUSE: Find the closest contour in the terrain
def mouse_contour_in_terrain(eye, target)
	vec = eye.vector_to target
	size = @view.pixels_to_model @mouse_precision, target
	t = (camera_in_situ?) ? @tr_tot : @tr_dessin_inv
	tinv = t.inverse	
	target = t * target
	
	hi_contour = nil
	lst_k = []
	dmin = size
	@lst_contours.each do |contour|
		d, iseg, pt, ptc = G6.proximity_point_curve_by_circle(target, contour.circles_3d, dmin)
		if d && d < dmin
			pt = tinv * ptc
			next if vec % eye.vector_to(pt) < 0
			lst_k.push [eye.distance(pt), contour]
			dmin = d
		end
	end
	
	return nil if lst_k.empty?
	lst_k = lst_k.sort { |a, b| a.first <=> b.first }
	lst_k[0][1]
end

	
def zoom_target ; @zoom_target ; end
def current_altitude ; @current_altitude ; end
	
def which_node(pt)
	pt =  @tr_bello_inv * pt
	
	dx = (x - @xmin) / @dx
	dy = (y - @ymin) / @dy
	ix = dx.round
	iy = dy.round
	((dx - ix).abs < 0.2 && (dy - iy).abs < 0.2) ? @grid_nodes[iy * (@nx+1) + ix] : nil
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# CLICK: Click Management
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#CLICK: Button click DOWN
def onLButtonDown(flags, x, y, view)
	@hi_contour_down = @hi_contour
end

#CLICK: Button click UP - Means that we end the selection
def onLButtonUp(flags, x, y, view)
	
	#Select or unselect a contour
	if @hi_contour && @hi_contour == @hi_contour_down
		contour_toggle_select @hi_contour
	elsif !@hi_contour
		contour_select_all false
	end
end

#CLICK: Double Click received
def onLButtonDoubleClick(flags, x, y, view)
	if @hi_contour
		contour_select_all true
	end	
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# BOITE: Bounding boxes for catching selection
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#Calculate all boites
def boite_calculation
	@ndiv_x = 15
	@ndiv_y = 15
	@ndiv_z = 5
	@hsh_boites = {}
	
	#Creating the main boite
	@boite_main = @struct_Topo_Boite.new
	@boite_main.bb = @bb_3d	
	boite_instantiate @boite_main
	
	#Creating the other boites
	for ix in 0..@ndiv_x
		for iy in 0..@ndiv_y
			for iz in 0..@ndiv_z
				boite = @struct_Topo_Boite.new
				boite.bb = Geom::BoundingBox.new
				boite.key = "#{ix}-#{iy}-#{iz}"
				@hsh_boites[boite.key] = boite
			end
		end
	end	
end

#BOITE: Instantiate a boite bounding box based on nodes it contains
def boite_instantiate(boite)
	corners = [[0, 4, 6, 2], [1, 5, 7, 3], [0, 1, 5, 4], [2, 3, 7, 6], [0, 1, 3, 2], [4, 5, 7, 6]]
	bb = boite.bb
	boite.faces_3du = corners.collect { |a| a.collect { |i| bb.corner(i) } }
end

#BOITE: Check if a ray intersect a boite
def boite_intersect_ray?(view, boite, ptxy)
	return false unless boite.faces_2d
	boite.faces_2d.each do |pts2d|
		return true if Geom.point_in_polygon_2D(ptxy, pts2d, true)
	end	
	false
end

#BOITE: Find the boite containing the point pt
def boite_locate_point(pt)
	ix = ((pt.x - @xmin) / (@xmax - @xmin) * @ndiv_x).floor
	iy = ((pt.y - @ymin) / (@ymax - @ymin) * @ndiv_y).floor
	begin
		iz = ((@zmin - @zmax).abs < 0.001) ? 0 : ((pt.z - @zmin) / (@zmax - @zmin) * @ndiv_z).floor
		#iz = (@zmin == @zmax) ? 0 : ((pt.z - @zmin) / (@zmax - @zmin) * @ndiv_z).floor
	rescue
		puts "NAN ZMIN = #{@zmin}" if @zmin.nan?
		puts "NAN ZMAX = #{@zmax}" if @zmax.nan?
		iz = 0
	end	
	ix = 0 if ix < 0
	iy = 0 if iy < 0
	iz = 0 if iz < 0
	key = "#{ix}-#{iy}-#{iz}"
	@hsh_boites[key]
end

#----------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------
# TOGGLE: Set or unset properties for contours and repairs
#----------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------

#TOGGLE: Notify a change of contour parameter
def notify_contour_set(code, val, contours=nil)
	lst_contours = (contours) ? contours : @lst_contours
	case code
	when :cliff
		option_change_cliff val, lst_contours unless lst_contours.empty?
	when :hilltop
		option_change_hilltop val, lst_contours unless lst_contours.empty?
	end
	contour_select_all false 
	@view.invalidate
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# DESSIN: Drawing routine for GUI
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#DESSIN: Top method to draw the surface and map
def dessin(view)
	return unless @dessin_info
	dessin_compute_at_view
	dessin_surface view
	dessin_wall view
	dessin_contours_situ view
	dessin_map view
	dessin_cell view, @hi_cell
	dessin_hi_contour view, @hi_contour
	dessin_selected_contours view
	dessin_hi_node view if @hsh_options[:debug_zone]
	dessin_debug view
end

def dessin_debug(view)
	#dessin_boite(view)
	#dessin_circles(view, @hull_contour)
end

def dessin_circles(view, contour)
	return unless contour && contour.circles
	view.line_stipple = ''
	view.line_width = 1
	view.drawing_color = 'blue'
	contour.circles.each do |circle|
		ptcenter, radius, = circle
		pi = Math::PI
		ptbeg = ptcenter.offset X_AXIS, radius
		pts = []
		for i in 0..24
			angle = i * pi / 12
			tr = Geom::Transformation.rotation ptcenter, Z_AXIS, angle
			pts.push tr * ptbeg
		end
		view.draw GL_LINE_LOOP, pts.collect { |pt| @tr_bello * pt }
	end	
end

def dessin_map(view)
	return if camera_in_situ?
	dessin_contours view
	dessin_grid view
	dessin_contours_xy view
	dessin_arlequin view
	dessin_hull view
end

def dessin_hi_node(view)
	node = @hi_node
	return unless node
	dessin_node view, node
	#text_node node
	node.node_friends.each { |nd| dessin_node view, nd, :friend } if node.node_friends
	if node.type == :single
		dessin_single_contour view, [node.ref_contour], :single
		dessin_single_contour view, node.contours, :out
	else	
		dessin_single_contour view, node.contours, node.type
	end	
end

def dessin_contours(view)
	return unless @dessin_info
	lines = @dessin_info.contour_lines
	return unless lines
	view.line_stipple = ''
	view.line_width = 2
	view.drawing_color = @color_contour_algo
	lines.each { |pts| view.draw GL_LINE_STRIP, pts }
end

def dessin_contours_situ(view)
	return unless @dessin_info && @dessin_info.in_situ
	lines = @contour_lines_situ_ds
	return unless lines
	view.line_stipple = ''
	view.line_width = 2
	view.drawing_color = @color_contour_algo
	lines.each { |pts| view.draw GL_LINE_STRIP, pts }
end

def dessin_contours_xy(view)
	return unless @dessin_info
	lines = @contour_lines_xy_ds
	return unless lines
	view.line_stipple = ''
	view.line_width = 2
	view.drawing_color = @color_contour_algo
	lines.each { |pts| view.draw GL_LINE_STRIP, pts }
end

def dessin_grid(view)
	return unless @dessin_info
	gridlines = @dessin_info.grid_lines
	return unless gridlines
	view.line_stipple = '-'
	view.line_width = 1
	view.drawing_color = 'gray'
	view.draw GL_LINES, gridlines
end

def dessin_arlequin(view)
	return unless @dessin_info
	tri0 = @dessin_info.arlequin0
	return unless tri0
	tri1 = @dessin_info.arlequin1
	view.drawing_color = 'lightyellow'
	view.draw GL_TRIANGLES, tri0
	view.drawing_color = 'lightgreen'
	view.draw GL_TRIANGLES, tri1
end

def dessin_hull(view)
	return unless @dessin_info
	lines = @dessin_info.hull_lines
	return unless lines
	view.line_stipple = ''
	view.line_width = 3
	view.drawing_color = @color_hull
	view.draw GL_LINE_STRIP, lines
end

def dessin_hull_native(view)
	return unless @dessin_info
	view.drawing_color = 'green'
	view.draw GL_LINE_STRIP, @hull_pts_lines
	
	@hull_pts_lines.each do |pt|
		pt2d = view.screen_coords pt
		pts = G6.pts_square pt2d.x, pt2d.y, 3
		view.draw2d GL_POLYGON, pts
	end	
end

def dessin_surface(view)
	return unless @dessin_info
	tri = @surface_triangles_ds
	return unless tri && tri.length > 0
	view.drawing_color = (@dessin_info.in_situ) ? @color_surface : @color_surface_working
	view.draw GL_TRIANGLES, tri
	view.line_stipple = ''
	view.line_width = 1
	view.drawing_color = 'gray'
	view.draw GL_LINES, @surface_lines_ds if @surface_lines_ds && @surface_lines_ds.length > 0
end

def dessin_wall(view)
	return unless @dessin_info
	quads = @wall_quads_ds
	return unless quads && quads.length > 0
	view.drawing_color = @color_wall
	view.draw GL_QUADS, quads
	view.line_stipple = '.'
	view.line_width = 1
	view.drawing_color = 'gray'
	view.draw GL_LINES, @wall_lines_ds
end

def dessin_node(view, node, type_color=nil)
	return unless node
	
	#Marking the node
	dxp = @dx * 0.3
	dyp = @dy * 0.3
	pt0 = node.ptxy.offset Z_AXIS, 1
	pt1 = pt0.offset(X_AXIS, -dxp * 0.5).offset(Y_AXIS, -dyp * 0.5)
	pt2 = pt1.offset X_AXIS, dxp
	pt3 = pt2.offset Y_AXIS, dyp
	pt4 = pt1.offset Y_AXIS, dyp
	type_color = node.type unless type_color
	view.drawing_color = @hsh_node_colors[type_color]
	#view.draw GL_POLYGON, [pt1, pt2, pt3, pt4].collect { |pt| G6.small_offset(view, @tr_bello * pt) }
	view.draw2d GL_POLYGON, [pt1, pt2, pt3, pt4].collect { |pt| view.screen_coords(@tr_bello * pt) }
end

def text_node(node)
	return unless node
	
	ix = node.ix
	iy = node.iy
	k = iy * (@nx+1) + ix
	text = "in=#{node.inside_hull}"
	text += "key_x = #{@key_x[k].inspect} key_y = #{@key_y[k].inspect}"
	if node.zone
		text += "- zone = #{node.zone.lsk.inspect} - #{node.zone.object_id}"
	else
		text += "No zone"
	end	
	text += " PT" if node.pt
	text += "  ix=#{node.ix} iy=#{node.iy} type = #{node.type.inspect}"
	if node.contours
		lsk = node.contours.collect { |c| c.inum }
		text += "  contours = #{lsk.inspect}"
	end	
	text += node.reasons.join(' + ') if node.reasons
	Sketchup.set_status_text text
end

def dessin_boite(view)
	return unless @selected_boites
	view.drawing_color = 'green'
	view.line_width = 2
	view.line_stipple = ''
	@boite_main.faces_2d.each do |pts|
		#view.draw2d GL_POLYGON, pts
		view.draw2d GL_LINE_LOOP, pts
	end	
	view.drawing_color = 'red'
	@selected_boites.each do |boite|
		boite.faces_3d.each do |pts|
			view.draw GL_LINE_LOOP, pts
		end	
	end	
end

#DESSIN: Draw a cell
def dessin_cell(view, cell, color=nil)
	return unless cell && cell.triangles
	view.drawing_color = (color) ? color : @color_cell
	if @dessin_info.in_situ
		t = @tr_tot_inv
	else
		t = @tr_dessin
		if @on_black
			view.draw2d GL_POLYGON, cell.ptsxy.collect { |pt| view.screen_coords(@tr_bello * pt) }
		else	
			view.draw GL_POLYGON, cell.ptsxy.collect { |pt| @tr_bello * pt }
		end	
	end	
	if @on_black
		cell.triangles.each { |tri| view.draw2d GL_POLYGON, tri.collect { |pt| view.screen_coords(t * pt) } }
	else	
		cell.triangles.each { |tri| view.draw GL_POLYGON, tri.collect { |pt| G6.small_offset(view, t * pt) } }
	end	
end

#DESSIN: Draw the tooltip box
def dessin_boxinfo(view, x, y)
	return unless @hi_contour
	text = @hi_contour.boxinfo
	G6.draw_rectangle_multi_text view, x, y, text, @hsh_boxinfo_contour
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# DESSIN: Computing routines for GUI
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

def dessin_compute
	vbar_progression :prepare_dessin
	@dessin_info = Topo_Dessin.new unless @dessin_info
	@dessin_info.done = false
	dessin_compute_contours
	dessin_compute_arlequin
	dessin_compute_hull
	dessin_compute_grid
	dessin_compute_surface
	dessin_compute_wall
	@dessin_info.in_situ = nil
	#dessin_compute_at_view
	@dessin_info.done = true
	dessin_compute_at_view
end

def dessin_update(cells)
	vbar_progression :update_dessin
	@dessin_info.done = false
	dessin_update_surface cells
	dessin_compute_wall
	dessin_update_at_view cells
	@dessin_info.done = true
end

def dessin_compute_at_view
	return unless @dessin_info && @dessin_info.done 
	in_situ = camera_in_situ?
	return if in_situ == @dessin_info.in_situ && @dessin_info.done
	@dessin_info.in_situ = in_situ
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	
	#Updating the lines for wall and terrain surface
	@wall_quads_ds = @dessin_info.wall_quads.collect { |pt| t * pt }
	@surface_triangles_ds = @dessin_info.surface_triangles.collect { |pt| t * pt }
	
	#Updating the boite coordinates
	@boite_main.faces_3d = @boite_main.faces_3du.collect { |pts| pts.collect { |pt| t * pt } }
	@lst_boites.each { |boite| boite.faces_3d = boite.faces_3du.collect { |pts| pts.collect { |pt| t * pt } } }
	@wall_boites.each { |boite| boite.faces_3d = boite.faces_3du.collect { |pts| pts.collect { |pt| t * pt } } }
	
	#Updating with current view
	dessin_update_when_view_changed_async
	dessin_refresh_when_view_changed
	@dessin_info.done = true
end

#DESSIN COMPUTE: update the lines and triangles for drawing when there is an update
def dessin_update_at_view(cells=nil)
	cells = @lst_cells_geom unless cells
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	@wall_quads_ds = @dessin_info.wall_quads.collect { |pt| t * pt }
	@wall_lines_ds = @dessin_info.wall_lines.collect { |pt| G6.small_offset @view, t * pt }
	@wall_boites.each { |boite| boite.faces_3d = boite.faces_3du.collect { |pts| pts.collect { |pt| t * pt } } }
	
	t0 = Time.now
	cells.each do |cell|
		next unless cell.triangles
		array_transfer(@surface_triangles_ds, @dessin_info.surface_triangles, *cell.ipos_tri) { |pt| t * pt }
		array_transfer(@surface_lines_ds, @dessin_info.surface_lines, *cell.ipos_lines) { |pt| G6.small_offset @view, t * pt }
	end	
	dessin_update_when_view_changed_async
	dessin_refresh_when_view_changed
end

#DESSIN COMPUTE: Immediate refresh when view changes
def dessin_refresh_when_view_changed
	@boite_main.faces_2d = @boite_main.faces_3d.collect { |pts| pts.collect { |pt| ptxy = @view.screen_coords(pt) ; ptxy.z = 0 ; ptxy } }
	@lst_boites.each { |boite| boite.faces_2d = boite.faces_3d.collect { |pts| pts.collect { |pt| @view.screen_coords pt } } }
	@wall_boites.each { |boite| boite.faces_2d = boite.faces_3d.collect { |pts| pts.collect { |pt| @view.screen_coords pt } } }
end

#DESSIN COMPUTE: Deferred refresh when view changes
def dessin_update_when_view_changed_async
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	@surface_lines_ds = @dessin_info.surface_lines.collect { |pt| G6.small_offset @view, t * pt }
	@wall_lines_ds = @dessin_info.wall_lines.collect { |pt| G6.small_offset @view, t * pt }
	@boite_main.faces_3d = @boite_main.faces_3du.collect { |pts| pts.collect { |pt| t * pt } }
	@contour_lines_xy_ds = @dessin_info.contour_lines_xy.collect { |line| line.collect { |pt| G6.small_offset @view, pt } }
	@contour_lines_situ_ds = @dessin_info.contour_lines_situ.collect { |line| line.collect { |pt| G6.small_offset @view, pt } }
end

#DESSIN COMPUTE: Compute the lines for the Contours
def dessin_compute_contours
	@dessin_info.contour_lines = []
	@dessin_info.contour_lines_situ = []
	@lst_contours.each do |contour| 
		#lines = contour.pts.collect { |pt| @tr_dessin * pt }
		#contour.lines_ds = lines
		@dessin_info.contour_lines.push contour.pts.collect { |pt| @tr_dessin * pt }
		@dessin_info.contour_lines_situ.push contour.pts.collect { |pt| @tr_tot_inv * pt }
	end	
	@dessin_info.contour_lines_xy = []
	@lst_contours.each do |contour| 
		lines = contour.ptsxy.collect { |pt| @tr_bello * pt }
		contour.lines_xy = lines
		@dessin_info.contour_lines_xy.push lines
	end	
end

#DESSIN COMPUTE: Compute the lines for the Contours
def dessin_compute_grid
	gridlines = @dessin_info.grid_lines = []
	for ix in 0..@nx
		x = @xmin + ix * @dx
		gridlines.push @tr_bello * Geom::Point3d.new(x, @ymin, 0), @tr_bello * Geom::Point3d.new(x, @ymax, 0)
	end	
	for iy in 0..@ny
		y = @ymin + iy * @dy
		gridlines.push @tr_bello * Geom::Point3d.new(@xmin, y, 0), @tr_bello * Geom::Point3d.new(@xmax, y, 0)
	end	
end

#DESSIN COMPUTE: Compute the lines for the Contours
def dessin_compute_hull
	@dessin_info.hull_lines = @hull_grid_pts.collect { |pt| @tr_bello * pt }
	@hull_pts_lines = @hull_pts.collect { |pt| @tr_bello * pt }
end

def dessin_compute_arlequin
	triangles0 = @dessin_info.arlequin0 = []
	triangles1 = @dessin_info.arlequin1 = []
	zdec = -@hull_proximity * 0.05
	zdec = -0.1
	#zdec = 0

	n = @lst_cells.length
	@lst_cells.each_with_index do |cell, i|
		next unless cell.triangles
		tri = cell.triangles.flatten.collect { |pt| @tr_bello * Geom::Point3d.new(pt.x, pt.y, zdec) }
		triangles = (cell.even) ? triangles0 : triangles1
		triangles.push *tri
		cell.triangles_xy = tri
	end	
end

def dessin_compute_surface
	triangles = @dessin_info.surface_triangles = []
	lines = @dessin_info.surface_lines = []
	n = @lst_cells.length
	@lst_cells.each_with_index do |cell, i|
		next unless cell.triangles
		vbar_mini_progression(n, i, 2000, 500)		
		tri = cell.triangles.flatten
		cell.ipos_tri = [triangles.length, tri.length]
		triangles.push *tri
		cell.ipos_lines = [lines.length, cell.lines.length]
		lines.push *(cell.lines)
	end	
end

def dessin_update_surface(cells)
	triangles = @dessin_info.surface_triangles
	lines = @dessin_info.surface_lines
	n = cells.length
	cells.each_with_index do |cell, i|
		next unless cell.triangles
		vbar_mini_progression(n, i, 2000, 500)		
		tri = cell.triangles.flatten
		array_assign triangles, tri, *cell.ipos_tri
		array_assign lines, cell.lines, *cell.ipos_lines
	end	
end

#DESSIN COMPUTE: Wall of the terrain
def dessin_compute_wall
	@dessin_info.wall_quads = @wall_quads
	@dessin_info.wall_lines = @wall_lines
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# VCB: Handling VCB inputs
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
	
def enableVCB? ; true ; end
	
#VCB: Declare vcb formats
def init_VCB	
	@vcb = Traductor::VCB.new
	@vcb.declare_input_format :nx, "i"
	@vcb.declare_input_format :dx, "l"
end

#VCB: Handle VCB input
def handle_VCB(text, view)
	return UI.beep unless @vcb
	nb_errors = @vcb.process_parsing(text)
	
	if nb_errors > 0
		lmsg = []
		@vcb.each_error { |symb, s| lmsg.push "<#{s}>" }
		msg = "#{T6[:T_ERROR_InVCB]} \"#{text}\"  --> #{lmsg.join(' - ')}"
		@palette.set_message msg, 'E'
		view.invalidate
	else
		action_from_VCB(text)
	end	
end

#VCB: Execute actions from the VCB inputs for the grid dimensions	
def action_from_VCB(text)
	lvalue = []
	@vcb.each_result do |symb, val, suffix|
		lvalue.push [symb, val]
	end
	
	grid_check_dimension_as_VCB(lvalue, text)	
end

end	#class TopoShaperAlgo

end	#End Module F6_TopoShaper
