=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed December 2014 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			:   TopoShaperCloudAlgo.rb
# Original Date	:   8 Dec 2014 
# Description	:   Algorithm for terrain generation from a cloud of points
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_TopoShaper

T6[:VGBAR_Triangulation] = "Triangulation"
T6[:VGBAR_Refinement] = "Refinement"
T6[:VGBAR_Interpolation] = "Interpolation"
T6[:VGBAR_Generation] = "Generation"
T6[:VGBAR_Title] = "Cloud Points Triangulation"
T6[:VGBAR_Iteration] = "Iteration"
T6[:VGBAR_IsoContours] = "Creating Iso Contour"

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# class PointCloudTriangulation: Adaptive triangulation of Point Cloud
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

class PointCloudTriangulation

#Parameters storage by original group
@@hsh_param_groups = {} unless defined?(@@hsh_param_groups)

#Structure for a Cloud point
CloudPoint = Struct.new :id, :pt, :ptxy, :within_contour

#Structure for History of generation
HistoryStep = Struct.new :iteration, :lst_clp_triangles, :hsh_sharp_borders, :finished

#INIT: Class instance initialization
def initialize(lst_cpoints, tr=nil, contours=nil, holes=nil, group_ori=nil, *hargs)
	#Basic initialization
	@model = Sketchup.active_model
	@view = @model.active_view
	@rendering_options = @model.rendering_options
	@tr_id = Geom::Transformation.new
	tr = @tr_id unless tr
	@tr = tr
	
	@ph = @view.pick_helper
	@iteration = 0
	@fac_sharp_borders2 = 1.25
	@distmin_factor = 0.005
	@hull_method = :convex
	algo_reset
	
	#Initialization
	init_colors

	#Parsing the arguments
	hargs.each { |harg| harg.each { |key, value| parse_args(key, value) } if harg.class == Hash }
	
	#Computing the axes transformation
	compute_axes_transformation(contours, lst_cpoints)
	
	#Registering the initial cloud points
	@cloud_points_ori = lst_cpoints.collect { |pt| @tr_axe_inv * pt }
	cloud_points_cleanup
	
	#Checking the contours. If none, compute the convex hull
	@hsh_contours = {}
	if contours
		@hull_method = :face
	else
		@hull_method = :convex
		contours, holes = contour_compute_hull
	end
	@hsh_contours[@hull_method] = [contours, holes]
	
	#Registering the contours and holes
	contour_registration

	#Reporting the statistics
	@text_stats = "#{T6[:T_TXT_CloudPoints]} = #{@cloud_points_ori.length}"
	@text_stats += "\n#{T6[:T_TXT_Contours]} = #{contours.length}"
	@text_stats += " [#{holes.length}]" if holes && holes.length > 0
	
	#Initializing the parameters
	init_parameters
	load_parameters_from_group(group_ori)
end

#Parsing the arguments 
def parse_args(key, value)
	skey = key.to_s
	case skey
	when /suops/i
		@suops = value
	end	
end

#INIT: return a text stats on cloud points and contours / holes
def stats_info ; @text_stats ; end

#Initialize the colors
def get_colors ; @hsh_colors ; end
def init_colors
	@hsh_colors = { :sharp_border => 'darkorange', :sharp_border2 => 'red' }
	@color_sharp_border = MYDEFPARAM[:TPS_ColorSharp]
	@color_sharp_border2 = MYDEFPARAM[:TPS_ColorVerySharp]
	@color_sharp_border3 = MYDEFPARAM[:TPS_ColorTinyVerySharp]
	@color_surface = @color_terrain = MYDEFPARAM[:TPS_ColorTerrain]
	@use_color_terrain = MYDEFPARAM[:TPS_UseColorTerrain]
	@color_iso_contour = MYDEFPARAM[:TPS_ColorIso]
	@use_color_iso = MYDEFPARAM[:TPS_UseColorIso]
	@color_skirt = MYDEFPARAM[:TPS_ColorSkirt]
	@use_color_skirt = MYDEFPARAM[:TPS_UseColorSkirt]
end

#INIT: Cleanup the initial points for duplicates and very close points in X, Y
def cloud_points_cleanup
	delta = 0.01
	@cloud_points_ori = @cloud_points_ori.sort { |a, b| ((a.x - b.x).abs <= delta) ? a.y <=> b.y : a.x <=> b.x }
	
	#Computing the bounding box
	box = Geom::BoundingBox.new
	box.add @cloud_points_ori 
	@distmin = box.diagonal * @distmin_factor
	
	lst_clp = []
	n = @cloud_points_ori.length - 1
	davg = navg = 0
	for i in 0..n
		pti = @cloud_points_ori[i]
		ptj = @cloud_points_ori[i+1]
		next if pti == ptj
		next unless !ptj || (ptj.x - pti.x).abs > delta || (ptj.y - pti.y).abs > delta
		lst_clp.push pti
		if ptj 
			d = pti.distance(ptj)
			davg += d if d < @distmin
			navg += 1
		end	
	end
	davg = davg / navg if navg > 0
	@distmin = [davg, @distmin * 0.5].max if davg > 0
	@cloud_points_ori = lst_clp
end

#INIT: Compute the axes transformation
def compute_axes_transformation(contours, lcpoints)
	if contours && contours[0].length > 3
		pt0, pt1, pt2 = contours[0]
		vecx = pt0.vector_to(pt1)
		vecy = pt0.vector_to(pt2)
		@normal_ref = vecx * vecy
		@normal_ref = @normal_ref.reverse if @normal_ref % G6.transform_vector(Z_AXIS, @tr.inverse) < 0
	else
		@normal_ref = Z_AXIS
		pt0 = nil
		zmin = nil
		lcpoints.each do |pt| 
			if !zmin || pt.z < zmin
				pt0 = pt
				zmin = pt.z
			end
		end	
	end
	
	#Computing the Axes transformation
	@tr_axe = Geom::Transformation.axes(pt0, *@normal_ref.axes)
	@tr_axe_inv = @tr_axe.inverse
	@tr_draw = @tr * @tr_axe
	@tr_draw_inv = @tr_draw.inverse
	@pt_ref = @tr_draw_inv * pt0
end

#------------------------------------------
# CONTOUR: Manage contours
#------------------------------------------

#CONTOUR: Return the current hull method	
def contour_hull_method ; @hull_method ; end
def no_contour_face ; !@hsh_contours[:face] ; end

#CONTOUR: Register the contours
def contour_registration
	contours, holes = @hsh_contours[@hull_method]
	@clipping_contours = []
	@clipping_holes = []	
	if contours
		hplane = [ORIGIN, Z_AXIS]
		contours.each do |contour| 
			@clipping_contours.push contour.collect { |pt| (@tr_axe_inv * pt).project_to_plane hplane }
		end	
		if holes && !@clipping_contours.empty?
			holes.each do |hole| 
				@clipping_holes.push hole.collect { |pt| (@tr_axe_inv * pt).project_to_plane hplane }
			end	
		end	
	end	
end
	
#CONTOUR: Modify the contour method	
def contour_modify_method(hull_method, suops)
	return if hull_method == @hull_method
	@hull_method = hull_method
	contour_compute_hull
	contour_registration
	geometry_execute(suops) if @geometry_generated
end	

#CONTOUR: Compute a contour based on the method	
def contour_compute_hull
	#Checking if contours already computed
	contours = @hsh_contours[@hull_method]
	return contours if contours
	
	#Normalizing the points in XY plane
	normal = @normal_ref
	lcpoints_xy = G6.flatten_to_plane(normal, @cloud_points_ori)
	
	case @hull_method
	when :best_box
		progression_proc = proc { |i, out_of| @vgbar.progression(1.0 * i / out_of) if i.modulo(200) == 0 }
		hsh = { :delay => 0.5 }
		@vgbar = Traductor::VisualProgressBar.new T6[:MSG_CalcultingFittingBox], hsh
		@vgbar.start
		hulls = [Traductor::BestFit2d.best_fitting_box(normal, lcpoints_xy, nil, progression_proc)]
		@vgbar.stop
		
	when :concave
		concave_algo = Traductor::ConcaveHull2D.new
		hulls = concave_algo.compute(lcpoints_xy)
		
	else
		hull = Traductor::ConvexHull2D.compute(lcpoints_xy)
		hulls = [hull[0..-2]]
	end	
		
	#Returning the contour	
	contours = hulls.collect { |hull| hull.collect { |pt| @tr_axe * pt } }
	@hsh_contours[@hull_method] = [contours, nil]
end

#------------------------------------------
# PARAMETER: Manage parameters
#------------------------------------------

#PARAMETER: Initialize the parameters
def init_parameters
	#Computing the minimum and maximum altitudes
	iso_bounding_altitude
	@skirt_enabled = MYDEFPARAM[:TPS_OPTION_SkirtEnabled]
	@skirt_delta = 0
	@skirt_delta = 20 unless @hsh_contours[:face]
	@points_enabled = MYDEFPARAM[:TPS_OPTION_PointsEnabled]
	@points_style = (MYDEFPARAM[:TPS_OPTION_PointsLine]) ? :line : :only
	
	#Default for calculation parameters
	@roundness = MYDEFPARAM[:TPS_OPTION_Roundness]
	@deviation_max = MYDEFPARAM[:TPS_OPTION_DeviationMax]
	@triangles_max = MYDEFPARAM[:TPS_OPTION_TrianglesMax]
end

#PARAMETER: Store parameter
def parameter_store(key, value)
	@hsh_param_storage[key] = value
end

#PARAMETER: Load the parameters attached to the original group if any
def load_parameters_from_group(group)
	@group_ori = group
	if group && group.valid?
		id = group.entityID
	else
		id = 0
	end	
	@hsh_param_storage = @@hsh_param_groups[id]
	@hsh_param_storage = @@hsh_param_groups[id] = {} unless @hsh_param_storage
	
	@hsh_param_storage.each do |key, value|
		begin
			eval "@#{key} = value" 
		rescue
		end
	end
end

#----------------------------------------------
# CLOUDPOINT: Management of Cloud Points
#----------------------------------------------

#CLOUD_POINT: Create a Cloud Point object
def cloud_point_create(pt)
	clp_exist = @lst_cloud_points.find { |clp| clp.pt.distance(pt) < 0.1 }
	return clp_exist if clp_exist
	
	@icloud_points += 1
	clp = CloudPoint.new
	clp.pt = pt
	ptxy = clp.ptxy = Geom::Point3d.new(pt.x, pt.y, 0)
	clp.id = @icloud_points
	@lst_cloud_points.push clp
	
	clp.within_contour = point_within_contours?(ptxy)
	
	clp
end

#------------------------------------------------------------------
# PERIMETER: Check inclusion in Perimeter of contours and holes
#------------------------------------------------------------------

def loop_within_perimeter?(lpt)
	lptxy = lpt.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
	lshole = lptxy.find_all { |ptxy| point_within_holes?(ptxy, true) }
	return false if lshole.length == lptxy.length
	lptxy.each do |ptxy|
		return false if point_within_holes?(ptxy, false) || !point_within_contours?(ptxy)
	end	
	true
end

#ALGO: Check if a point is within the perimeter of contours and holes
def point_within_perimeter?(pt)
	ptxy = Geom::Point3d.new pt.x, pt.y, 0
	!point_within_holes?(ptxy) && point_within_contours?(ptxy)
end

#ALGO: Check if a point is within the contours (ignoring holes)
def point_within_contours?(ptxy)
	@clipping_contours.each do |contour|
		return true if Geom.point_in_polygon_2D(ptxy, contour, true)
	end
	false
end

#ALGO: Check if a point is within the holes
def point_within_holes?(ptxy, border=false)
	@clipping_holes.each do |hole|
		return true if Geom.point_in_polygon_2D(ptxy, hole, border)
	end
	false
end

#--------------------------------------------------------------
# PARAM: Management of calculation parameters
#--------------------------------------------------------------

#PARAM: Set the max number of triangles
def algo_triangles_max ; @triangles_max ; end	
def algo_set_triangles_max(triangles_max)
	@triangles_max = triangles_max
	parameter_store :triangles_max, triangles_max
	algo_analyze_param_impact
end

#PARAM: Set the smoothing max deviaition (in degrees)
def algo_deviation_max ; @deviation_max ; end	
def algo_set_deviation_max(deviation_max)
	@deviation_max = deviation_max
	parameter_store :deviation_max, deviation_max
	algo_analyze_param_impact
	drawing_prepare
end

#PARAM: Set the max number of triangles
def algo_roundness ; @roundness ; end	
def algo_set_roundness(roundness)
	@roundness = roundness
	parameter_store :roundness, roundness
	algo_analyze_param_impact
end

#PARAM: Analyze the impact of changed parameters
def algo_analyze_param_impact
	unless @hsh_param
		@start_over = true
		@force_compute = true
		@generate_enabled = true
		return
	end
	
	@start_over = false
	@force_compute = false
	
	if @roundness != @hsh_param[:roundness]
		@start_over = true
	end	

	if @deviation_max != @hsh_param[:deviation_max]
		@force_compute = true
		@start_over = true if @iteration == 0
	end	
	
	if @triangles_max != @hsh_param[:triangles_max]
		@force_compute = true
	end	
	
	if @iteration == @lst_history.length-1 && !@finished
		@force_compute = true	
	end
	
	@finished = false if @force_compute
	@generate_enabled = @iteration < @lst_history.length-1 || !@finished 
	@generate_enabled = false if @nb_sharp_borders == 0 && @nb_sharp_borders2 == 0
	@generate_enabled = true if @start_over || @force_recompute
	@generate_enabled = true if @iteration == 0
end

def algo_param_impact ; [@start_over, @force_compute] ; end
def algo_generate_enabled? ; @generate_enabled ; end
 
#--------------------------------------------------------------
# VGBAR: Management of visual progress bar
#--------------------------------------------------------------

def vgbar_init
	@nb_interpol = 50
	@nb_iterations = 30
	
	@hsh_step_text = {}
	@hsh_step_text[:calculate] = T6[:VGBAR_Triangulation]
	@hsh_step_text[:refine] = T6[:VGBAR_Refinement]
	@hsh_step_text[:recompute] = T6[:VGBAR_Interpolation]
	@hsh_step_text[:generate] = T6[:VGBAR_Generation]
	@nb_steps = @hsh_step_text.length 
	
	delay = 0.5
	mini_delay = 0.5
	hsh = { :delay => delay, :mini_delay => mini_delay, :style_color => :bluegreen, :interrupt => true }
	@bar_title = T6[:VGBAR_Title]
	@vgbar = Traductor::VisualProgressBar.new @bar_title, hsh
	@vgbar.start
end

#VGBAR: Main progression of the visual bar
def vgbar_progression(symb)
	return unless @vgbar
	@last_time_interpol = nil
	case symb
	when :calculate
		istep = 1
	when :refine
		istep = 2
	when :recompute
		istep = 0
	when :generate
		istep = 3
	end	
	
	if @iteration >= @nb_iterations
		@nb_iterations += 2
	end	
	nstep = @nb_steps
	ratio = 1.0 * istep / nstep 
	title = @bar_title + " - #{T6[:VGBAR_Iteration]} ##{@iteration}"
	title += " - #{@lst_clp_triangles.length} triangles" if @lst_clp_triangles && !@lst_clp_triangles.empty?
	@vgbar.update_title title
	@vgbar.progression ratio, @hsh_step_text[symb]
end

#VGBAR: Mini progression of the visual bar
def geometry_mini_progression_recompute(ipoint)
	return unless @vgbar
	t0 = Time.now
	return if @last_time_interpol && (t0 - @last_time_interpol) < 1.0
	@last_time_interpol = t0
	
	npoint = @lnew_points.length
	ratio = 1.0 * ipoint / npoint
	out_of = 1.0 / @nb_steps
	msg = "#{ipoint} / #{npoint}"
	@vgbar.mini_progression(ratio, out_of, msg)
end

#VGBAR: Mini progression of the visual bar
def geometry_mini_progression_triangulate(ratio)
	return unless @vgbar
	out_of = 1.0 / @nb_steps
	msg = "#{sprintf("%d", ratio * 100)}%"
	@vgbar.mini_progression(ratio, out_of, msg)
end

def geometry_finish
	algo_analyze_param_impact
	return unless @vgbar
	@vgbar.stop
	@vgbar = nil
end

#--------------------------------------------------------------
# TOP: Top execution
#--------------------------------------------------------------

#TOP: Top execution method
def algo_top_execute(suops, by_step)
	@suops = suops
	@hsh_param = algo_parameters_current
	init_colors
	
	#Using history instead
	unless @start_over || @force_compute
		if by_step
			history_execute suops, :next
			return true
		else	
			history_execute suops, :auto
			return true if algo_is_finished?
		end
	end

	#Updating the history
	history_adjust
	
	#Initialization
	@geometry_generated = false
	@by_step = by_step
	@iteration_max = 30
	@suops.abort_restart_operation
	@group_ori.visible = @rendering_options["DrawHidden"] if @group_ori
	geometry_erased
	
	begin
		status = algo_top_execute_protected
	rescue Exception => e
		status = e
		Traductor::RubyErrorDialog.invoke e, T6[:TIT_CloudOperation], T6[:T_TXT_Error]
	end	
	status
end

#TOP: Get the status of execution (useful for asynchronous mode)
def algo_status_execution
	@status_execution
end

def algo_iteration
	@iteration
end	
	
#TOP: Get the current parameters for calculation 	
def algo_parameters_current
	{ :roundness => @roundness, :deviation_max => @deviation_max, :triangles_max => @triangles_max }
end
	
#TOP: Top execution in error-protected mode	
def algo_top_execute_protected
	#Initialize the diagram if required	
	algo_init_diagram if @start_over
		
	#Starting the Visual Progress bar
	vgbar_init
	
	#Launching the robot
	@suops.start_execution { algo_robot }
end

#TOP: Robot method for execution
def algo_robot
	begin
		algo_robot_protected
	rescue Exception => e
		@status_execution = e
		@suops.abort_execution
		Traductor::RubyErrorDialog.invoke e, T6[:TIT_CloudOperation], T6[:T_TXT_Error]
	end	
end

#TOP: Check if algorithm has finished
def algo_is_finished?
	return false unless @iteration_max && @iteration
	@finished || @iteration > @iteration_max || @nb_total_triangles > @triangles_max || (@nb_sharp_borders == 0 && @nb_sharp_borders2 == 0)
end

#TOP: Robot state processor for the calculation and generation of terrain
def algo_robot_protected
	while(action, *param = @suops.current_step) != nil	
		case action
		
		when :_init
			return if @suops.yield?
			next_step = (@iteration == 0) ? :calculate : :recompute
		
		when :calculate
			return if @suops.yield?
			@iteration += 1
			vgbar_progression action
			algo_compute_triangles
			next_step = :refine
			
		when :refine
			return if @suops.yield?
			vgbar_progression action
			algo_refine_triangles
			drawing_prepare
			history_save
			next_step = (algo_is_finished? || @by_step) ? :generate : :recompute
			
		when :recompute
			return if @suops.yield?
			ipoint, = param
			ipoint = 0 unless ipoint
			vgbar_progression action if ipoint == 0
			geometry_mini_progression_recompute ipoint
			ipoint = algo_compute_new_point(ipoint)
			next_step = (ipoint) ? [:recompute, ipoint] : :calculate

		when :generate
			return if @suops.yield?
			vgbar_progression action
			geometry_generate
			next_step = :finish

		when :finish
			geometry_finish
			next_step = nil
			
		end
		
		break if @suops.next_step(*next_step)
	end
end

#----------------------------------------------
# ALGO: Management of algorithm
#----------------------------------------------

#ALGO: Reset the environment
def algo_reset
	@lst_cloud_points = []
	@icloud_points = -1
	@finished = false
	@iteration = 0
	@lst_clp_triangles = nil
	@nb_total_triangles = 0
	@group_terrain = nil
	@geometry_generated = false
	@generate_enabled = true
	@draw_sharp_borders = @draw_sharp_borders2 = nil
	@start_over = true
	history_init
end
	
def algo_interpolate(ptxy, lpt)
	G6.directional_average_multi_points(ptxy, lpt, @roundness)
end
	
#ALGO: Initialize the environment
def algo_init_diagram
	#Resetting the environment
	algo_reset
	
	#Saving the current parameters
	@prev_parameters = algo_parameters_current
	
	#Creating the cloud point based on the original points
	@cloud_points_ori.each do |pt|
		cloud_point_create pt
	end
	ls = @lst_cloud_points.find_all { |clp| clp.within_contour }
	
	#Creating cloud points on the contours and holes
	@clipping_contours.each do |contour|
		contour.each do |ptxy|
			z = algo_interpolate ptxy, @cloud_points_ori
			pt = Geom::Point3d.new ptxy.x, ptxy.y, z
			cloud_point_create(pt)
		end
	end	
	@newpoints_beg = 0
	@newpoints_end = @lst_cloud_points.length - 1
	
	#Creating the Delaunay algorithm class instance
	hsh = { :progression_number => 1000, :progression_proc => self.method("geometry_mini_progression_triangulate") }
	@delaunay_algo = Traductor::DelaunayTriangulation2D.new hsh
end

#ALGO: Calculate the triangulation by Delaunay method
def algo_compute_triangles
	#Delaunay triangulation 
	lpt = @lst_cloud_points[@newpoints_beg..@newpoints_end].collect { |clp| clp.pt}
	
	t0 = Time.now
	continue = (@newpoints_beg > 0) 
	itriangles = @delaunay_algo.calculate(lpt, continue)
	@nb_total_triangles = itriangles.length

	#Storing the results of new triangles
	@hsh_clp_triangles = []
	@lst_clp_triangles = []
	itriangles.each do |itri|
		lclp = itri.collect { |i| @lst_cloud_points[i] }
		lclp.each do |clp| 
			id = clp.id
			ls = @hsh_clp_triangles[id]
			ls = @hsh_clp_triangles[id] = [] unless ls
			ls.push lclp
		end	
		@lst_clp_triangles.push lclp
	end	
	duplicate_tri
end

def duplicate_tri
	hsh = {}
	@lst_clp_triangles.each do |lclp|
		itri = lclp.collect { |clp| clp.id }
		i, j, k = itri.sort
		key = "#{i} - #{j} - #{k}"
		hsh[key] = itri
	end	
end

#ALGO: Refine the triangulation
def algo_refine_triangles
	return unless @lst_clp_triangles
	t0 = Time.now
	
	#Finding the triangles within contours
	t2 = Time.now
	clp_triangles = @lst_clp_triangles.find_all { |lclp| lclp.find { |clp| clp.within_contour } }
	
	#Deviation angle between normals in degree
	ls_fac = [1.0, 1.4, 1.3, 1.1]
	fac = ls_fac[@iteration.modulo(4)]
	fac = 1 #if @by_step
	deviation_max = @deviation_max #* fac
	
	#Main loop on triangles, looking for sharp borders
	isharp = 0
	iborder = 0
	new_pt_info = []
	@hsh_sharp_borders = {}
	clp_triangles.each do |lclp|
		clp1, clp2, clp3 = lclp
		
		#Check deviation and border status for the triangle
		lstatus = []
		[[clp1, clp2], [clp1, clp3], [clp2, clp3]].each do |clpi, clpj|
			next unless clpi.within_contour || clpj.within_contour
			status = border_deviation deviation_max, clpi, clpj
			lstatus.push status
		end	
		
		lptxy = lclp.collect { |clp| clp.ptxy }
		pt1, pt2, pt3 = lptxy
		newpt = []
		
		#Check deviation of sides of triangle. If too sharp, create a new point in the center of the triangle
		langles = lstatus.find_all { |a| a.is_a?(Float) }
		unless langles.empty?
			new_pt_info.push [langles.max, G6.straight_barycenter(lptxy)]
			isharp += 1
		end	
			
		#For borders, add an external point, symmetrical of the other vertex by the border
		if lstatus.find { |a| a == :border }
			status12, status13, status23 = lstatus
			out1 = outer_vertex_sharp?(deviation_max, clp1)
			out2 = outer_vertex_sharp?(deviation_max, clp2)
			out3 = outer_vertex_sharp?(deviation_max, clp3)
			new_pt_info.push [0, border_new_point(pt1, pt2, pt3)] if status12 == :border && (out1 || out2)
			iborder += 1 if status12 == :border && (out1 || out2)
			new_pt_info.push [0, border_new_point(pt1, pt3, pt2)] if status13 == :border && (out1 || out3)
			iborder += 1 if status13 == :border && (out1 || out3)
			new_pt_info.push [0, border_new_point(pt2, pt3, pt1)] if status23 == :border && (out2 || out3)
			iborder += 1 if status23 == :border && (out2 || out3)		
		end
	end
	dt = Time.now - t0
	
	#Checking if the triangle limit is reached
	npossible = 0.9 * (@triangles_max - @nb_total_triangles)
	npoint = new_pt_info.length
	if npoint > npossible
		new_pt_info = new_pt_info.sort { |a, b| b.first <=> a.first }
		@lnew_points = new_pt_info[0..npossible].collect { |a| a[1] }
	else
		@lnew_points = new_pt_info.collect { |a| a[1] }
	end
	
	ntrimin = 2
	@finished = true if @lnew_points.length < ntrimin && @iteration > 5 && (@by_step || @iteration.modulo(1) == 0)
end

def algo_best_center(langles, lptxy)
	weights = langles.collect { |a| (a.is_a?(Float)) ? 2 : 1 }
	xsum = ysum = wsum = 0
	lptxy.each_with_index do |ptxy, i|
		w = weights[i]
		xsum += w * ptxy.x
		ysum += w * ptxy.y
		wsum += w
	end	
	Geom::Point3d.new(xsum / wsum, ysum / wsum, 0)
end

#ALGO: Interpolate the new points
def algo_compute_new_point(ipoint)
	t1 = Time.now
	pt = @lnew_points[ipoint]
	@newpoints_beg = @lst_cloud_points.length if ipoint == 0
	@newpoints_end = @lst_cloud_points.length - 1 unless pt
	return nil unless pt
	
	pt.z = algo_interpolate(pt, @cloud_points_ori)
	cloud_point_create(pt)
	ipoint+1
end

def algo_nb_triangles
	@nb_total_triangles
end

#ALGO: Return the number of sharp borders in 2 categories
def algo_sharp_borders_info_fac2 ; @fac_sharp_borders2 ; end
def algo_sharp_borders_info ; "#{@nb_sharp_borders}" ; end
def algo_sharp_borders2_info ; "#{@nb_sharp_borders2}" ; end

#----------------------------------------------
# UTIL: Utility methods
#----------------------------------------------

def border_new_point(pt1, pt2, pt3)
	ptproj = Geom.linear_combination 0.5, pt1, 0.5, pt2
	vec = pt3.vector_to ptproj
	d = pt3.distance ptproj
	ptproj.offset vec, d	
end

def border_deviation(deviation_max, clp1, clp2)
	lst_triangles = @hsh_clp_triangles[clp1.id]
	tri1, tri2 = lst_triangles.find_all { |clp_tri| clp_tri.include?(clp1) && clp_tri.include?(clp2) }
	
	if tri1 && tri2
		key = (clp1.id < clp2.id) ? "#{clp1.id}-#{clp2.id}" : "#{clp2.id}-#{clp1.id}"
		angle = @hsh_sharp_borders[key]
		d = clp1.pt.distance clp2.pt
		unless angle
			normal1 = normal_of_triangle tri1
			normal2 = normal_of_triangle tri2
			angle = normal1.angle_between(normal2).radians
			angle = 180 - angle if angle > 90
			@hsh_sharp_borders[key] = angle
		end	
		return angle if d > @distmin && angle > deviation_max #|| (angle > 90 && 180 - angle > deviation_max)
	else
		return :border
	end
	nil
end

def normal_of_triangle(clp_tri)
	pt1, pt2, pt3 = clp_tri.collect { |clp| clp.pt }
	vec12 = pt1.vector_to pt2
	vec13 = pt1.vector_to pt3
	vec12 * vec13
end

def outer_vertex_sharp?(deviation_max, clp)
	#return false unless clp.within_contour
	lst_triangles = @hsh_clp_triangles[clp.id]
	ltri_clp = lst_triangles.find_all { |clp_tri| clp_tri.include?(clp) }
	
	lnormals = ltri_clp.collect { |clp_tri| normal_of_triangle(clp_tri) }
	
	n = lnormals.length - 1
	for i in 0..n-1
		normal_i = lnormals[i]
		for j in i+1..n
			normal_j = lnormals[j]
			normal_j = normal_j.reverse if normal_i % normal_j < 0
			angle = normal_i.angle_between normal_j
			return true if angle.radians > deviation_max
		end	
	end
	false
end

#----------------------------------------------
# HISTORY: Management of History
#----------------------------------------------

#HISTORY: Initialization
def history_init
	@lst_history = []
	his = HistoryStep.new
	his.iteration = 0

	his.lst_clp_triangles = []
	his.hsh_sharp_borders = {}
	his.finished = false
	
	@lst_history[0] = his
end

#HISTORY: Readjust the history based on the current iteration
def history_adjust
	@lst_history = @lst_history[0..@iteration]
	@newpoints_beg = 0
	@newpoints_end = @lst_cloud_points.length - 1
	@finished = false
end

#HISTORY: Save state of generation
def history_save
	his = HistoryStep.new
	his.iteration = @iteration
	
	his.lst_clp_triangles = @lst_clp_triangles
	his.hsh_sharp_borders = @hsh_sharp_borders
	his.finished = algo_is_finished?
	
	@lst_history[@iteration] = his
end

def history_was_finished?
	@lst_history[-1].finished
end
	
#HISTORY: Restore a state
def history_restore(symb)
	@geometry_generated = false
	@nb_total_faces = 0
	new_iter = nil
	case symb
	when :reset
		new_iter = 0
	when :previous
		new_iter = @iteration - 1
	when :next
		new_iter = @iteration + 1
	when :auto
		new_iter = @lst_history.length - 1
	end
	return false unless new_iter
	
	his = @lst_history[new_iter]
	return false unless his
	
	#Restoring the state at iteration
	@iteration = new_iter
	@lst_clp_triangles = his.lst_clp_triangles
	@hsh_sharp_borders = his.hsh_sharp_borders
	
	#Computing the sharp borders
	drawing_prepare
	
	true
end

#HISTORY: Top execution method for History navigation
def history_execute(suops, symb)
	#Restoring the history
	return true unless history_restore(symb)
	
	#Launching the robot for generation
	geometry_execute(suops)
end

#HISTORY: Check if back button is enabled
def history_back_possible?
	(@iteration > 0 && @lst_history[@iteration-1] != nil)
end

def force_start_over
	@start_over = true
end
	
#----------------------------------------------
# ISO: Drawing methods
#----------------------------------------------

def iso_bounding_altitude
	lz = @cloud_points_ori.collect { |pt| pt.z }
	@zmin_ori = lz.min
	@zmax_ori = lz.max
	@iso_delta = iso_best_delta
end

def iso_best_delta
	niso = 10
	dz = (@zmax_ori - @zmin_ori) / 10.0
	dzn = (dz * 1000).round / 1000
	tx = Sketchup.format_length dzn
	tx = tx.sub(/\~/, '')
	dzn = Traductor.string_to_length(tx)
	dzn
end

#----------------------------------------------
# DRAWING: Drawing methods
#----------------------------------------------

#DRAW: toplevel drawing method
def draw(view)
	unless @geometry_generated
		drawing_contour view
		drawing_cloud_points view
		drawing_triangles view
		drawing_sharp_borders view 
	end
end

#DRAW: Draw the contours and holes
def drawing_contour(view)
	return unless @clipping_contours
	
	view.line_width = 3
	view.line_stipple = ''
	view.drawing_color = 'red'
	(@clipping_contours + @clipping_holes).each do |contour|
		pts = contour.collect { |pt| G6.small_offset(view, @tr_draw * pt) }
		view.draw GL_LINE_LOOP, pts
	end	
end

#DRAW: Draw the original cloud points
def drawing_cloud_points(view)
	return unless @lst_cloud_points
	quads = []
	lines = []
	@cloud_points_ori.each do |pt|
		ptxy = Geom::Point3d.new pt.x, pt.y, 0
		size = size_from_pixel view, 4, ptxy
		quad = G6.pts_square(ptxy.x, ptxy.y, size)
		quads.concat quad
		lines.push @tr_draw * ptxy, @tr_draw * pt
	end
	
	view.line_width = 1
	view.line_stipple = '_'
	view.drawing_color = 'gray'
	view.draw2d GL_QUADS, quads.collect { |pt| view.screen_coords(@tr_draw * pt) }
	view.draw GL_LINES, lines	
end

def size_from_pixel(view, pixel, pt)
	view.pixels_to_model(pixel, @tr_draw * pt)
end

#DRAW: Draw the triangles
def drawing_triangles(view)
	return unless @lst_clp_triangles
	
	view.drawing_color = @color_surface
	view.draw GL_TRIANGLES, @draw_triangles if @draw_triangles && !@draw_triangles.empty?
	
	view.line_stipple = '_'
	view.line_width = 1
	view.drawing_color = 'gray'
	view.draw GL_LINES, @draw_lines.collect { |pt| G6.small_offset view, pt }  if @draw_lines && !@draw_lines.empty?
end

#DRAW: Draw the sharp borders
def drawing_sharp_borders(view)
	view.line_stipple = ''
	if @draw_sharp_borders && @draw_sharp_borders.length > 0 
		view.line_width = 1
		view.drawing_color = @color_sharp_border
		view.draw GL_LINES, @draw_sharp_borders.collect { |pt| G6.small_offset view, pt, 2 }
	end
	
	if @draw_sharp_borders2 && @draw_sharp_borders2.length > 0
		view.line_width = 2
		view.drawing_color = @color_sharp_border2
		view.draw GL_LINES, @draw_sharp_borders2.collect { |pt| G6.small_offset view, pt, 2 }
	end
	
	if @draw_sharp_borders3 && @draw_sharp_borders3.length > 0
		view.line_width = 2
		view.drawing_color = @color_sharp_border3
		view.draw GL_LINES, @draw_sharp_borders3.collect { |pt| G6.small_offset view, pt, 2 }
	end	
end

#DRAW: Draw the triangles
def drawing_prepare
	@draw_triangles = []
	@draw_lines = []
	@draw_sharp_borders = []
	@draw_sharp_borders2 = []
	@draw_sharp_borders3 = []
	@nb_sharp_borders = 0
	@nb_sharp_borders2 = 0
	@nb_sharp_borders3 = 0
	return unless @lst_clp_triangles
	
	#Terrain surface
	@lst_clp_triangles.each do |triclp|
		lpt = triclp.collect { |clp| clp.pt }
		next if lpt.find { |pt| !point_within_perimeter?(pt) }
		lpt = lpt.collect { |pt| @tr_draw * pt }
		@draw_triangles.concat lpt
		@draw_lines.push lpt[0], lpt[1], lpt[1], lpt[2], lpt[2], lpt[0]
	end
	
	#Highlights of sharp borders
	if @hsh_sharp_borders
		devmax = @deviation_max
		devmax2 = @deviation_max * @fac_sharp_borders2
		@hsh_sharp_borders.each do |key, angle|
			key =~ /(\d+)-(\d+)/
			clp1 = @lst_cloud_points[$1.to_i]
			clp2 = @lst_cloud_points[$2.to_i]
			next unless clp1.within_contour && clp2.within_contour
			next if angle <= devmax
			a = [@tr_draw * clp1.pt, @tr_draw * clp2.pt]
			d = clp1.pt.distance clp2.pt
			ptmid = Geom.linear_combination 0.5, clp1.ptxy, 0.5, clp2.ptxy
			next unless point_within_perimeter?(ptmid)
			if angle > devmax2
				if d < @distmin
					@draw_sharp_borders3.push *a 
					@nb_sharp_borders3 += 1
				else
					@draw_sharp_borders2.push *a 
					@nb_sharp_borders2 += 1
				end	
			elsif angle > devmax	
				if d < @distmin
					#@draw_sharp_borders3.push *a 
					#@nb_sharp_borders3 += 1
				else	
					@draw_sharp_borders.push *a 
					@nb_sharp_borders += 1
				end	
			end	
		end
	end
end

#------------------------------------------------------------
# GEOMETRY: Parameters for the Generation of the terrain
#------------------------------------------------------------

#GEOMETRY: Isocontours management
def geometry_iso_enabled? ; @iso_enabled ; end
def geometry_iso_delta? ; @iso_delta ; end
def geometry_toggle_iso ; geometry_set_iso(!@iso_enabled, @iso_delta) ; end 
def geometry_set_iso(enabled, delta)
	@iso_enabled = enabled
	parameter_store :iso_enabled, enabled
	if delta
		@iso_delta = delta
		parameter_store :iso_delta, delta
	end	
	geometry_iso_contours true
end

#GEOMETRY: Skirt management
def geometry_skirt_enabled? ; @skirt_enabled ; end
def geometry_skirt_delta? ; @skirt_delta ; end
def geometry_toggle_skirt ; geometry_set_skirt(!@skirt_enabled, @skirt_delta) ; end 
def geometry_set_skirt(enabled, delta)
	@skirt_enabled = enabled
	parameter_store :skirt_enabled, enabled
	if delta
		@skirt_delta = delta
		parameter_store :skirt_delta, delta
	end	
	geometry_skirt
end

#GEOMETRY: Cloud point management
def geometry_points_enabled? ; @points_enabled ; end
def geometry_points_style? ; @points_style ; end
def geometry_toggle_points ; geometry_set_points(!@points_enabled, @points_style) ; end 
def geometry_set_points(enabled, style)
	@points_enabled = enabled
	parameter_store :points_enabled, enabled
	if style
		@points_style = style
		parameter_store :points_style, style
	end
	geometry_points
end

#----------------------------------------------
# GEOMETRY: Generation of the terrain
#----------------------------------------------

#GEOMETRY: Robot for geometry generation
def geometry_execute(suops)
	@suops = suops
	@suops.abort_restart_operation
	@geometry_generated = false
	geometry_erased
	
	begin
		status = @suops.start_execution { generate_robot}
	rescue Exception => e
		status = e
		Traductor::RubyErrorDialog.invoke e, T6[:TIT_CloudOperation], T6[:T_TXT_Error]
	end	
	status
end

#GEOMETRY: Robot state processor for the generation of terrain
def generate_robot
	begin
		while(action, *param = @suops.current_step) != nil	
			case action
			
			when :_init
				next_step = :generate
			
			when :generate
				return if @suops.yield?
				geometry_generate unless @iteration == 0
				next_step = :finish

			when :finish
				geometry_finish
				next_step = nil
				
			end
			
			break if @suops.next_step(*next_step)
		end
		
	rescue Exception => e
		status = e
		Traductor::RubyErrorDialog.invoke e, T6[:TIT_CloudOperation], T6[:T_TXT_Error]
	end	
end

#GEOMETRY: Return the top group generated
def geometry_top_group ; @group_terrain ; end

#GEOMETRY: Check if geometry is currently generated
def geometry_generated? ; @geometry_generated ; end

#GEOMETRY: Get the number of faces in the terrain
def geometry_number_faces 
	@nb_total_faces
end

#GEOMETRY: Generate the geometry
def geometry_generate
	#Groups
	@group_iso = @group_points = @group_skirt = nil
	@top_entities = Sketchup.active_model.active_entities
	@group_terrain = @top_entities.add_group
	gent = @group_terrain.entities
	@nb_total_faces = 0
	
	#Generating the terrain surface
	duplicate_tri
	mesh = Geom::PolygonMesh.new
	@lst_clp_triangles.each do |lclp|
		poly3d = lclp.collect { |clp| @tr_draw * clp.pt }
		mesh.add_polygon poly3d
	end	
	gent.fill_from_mesh mesh, true, 12
	
	#Creating the intersection with contour
	geometry_intersection(gent)
	
	#Drawing the contours and holes in 2D
	@clipping_contours.each do |contour|
		pts = contour.collect { |pt| @tr_draw * pt }
		#gent.add_face pts
	end
	@clipping_holes.each do |hole|
		pts = hole.collect { |pt| @tr_draw * pt }
		#face = gent.add_face pts
		#face.erase!
	end
	
	#Creating the cloud points
	geometry_points
	
	#Drawing the iso_contours
	geometry_iso_contours
	
	#Skirt
	geometry_skirt 
	
	#Computing the number of triangles after clipping
	@nb_total_faces = @group_terrain.entities.grep(Sketchup::Face).length

	#Geometry is generated
	@geometry_generated = true	
end

#GEOMETRY: Intersect the surface with the contours and holes
def geometry_intersection(gent)
	#Computing the min and max altitude
	@bbox_clp = Geom::BoundingBox.new
	@lst_cloud_points.each do |clp|
		@bbox_clp.add clp.pt
	end
	@ptmin = @bbox_clp.min
	@ptmax = @bbox_clp.max
	@zmin = @bbox_clp.min.z
	@zmax = @bbox_clp.max.z
	dz = (@zmax - @zmin) * 0.1
	dz = 100 if dz < 0.000001
	zmin = @zmin - dz
	zmax = @zmax + dz
	
	#Performing the intersection
	border_edges = []
	(@clipping_contours + @clipping_holes).each do |contour|
		gr = @top_entities.add_group
		entities = gr.entities
		n = contour.length-1
		for i in 0..n
			pti = contour[i]
			j = (i == n) ? 0 : i+1
			ptj = contour[j]
			next if pti == ptj
			ptmin_i = Geom::Point3d.new pti.x, pti.y, zmin
			ptmax_i = Geom::Point3d.new pti.x, pti.y, zmax
			ptmin_j = Geom::Point3d.new ptj.x, ptj.y, zmin
			ptmax_j = Geom::Point3d.new ptj.x, ptj.y, zmax
			pts = [ptmin_i, ptmin_j, ptmax_j, ptmax_i].collect { |pt| @tr_draw * pt }
			face = entities.add_face pts
		end	
		edges = gent.intersect_with false, @tr_id, gent, @tr_id, false, gr
		####edges.each { |e| e.find_faces }
		border_edges.concat edges
		gr.erase!
	end
	
	#Cleaning up the faces
	hborder_edges = {}
	###border_edges.each { |e| e.find_faces if e.valid? }
	border_edges.each { |e| hborder_edges[e.entityID] = e if e.valid? }
	lerase = []
	gent.grep(Sketchup::Face).each do |face|
		ptvx = face.vertices.collect { |vx| @tr_draw_inv * vx.position }
		face.material = @color_terrain if @use_color_terrain
		ptmid = G6.straight_barycenter ptvx
		next if loop_within_perimeter?(ptvx) && point_within_contours?(ptmid)
		led = face.edges.find_all { |e| hborder_edges[e.entityID] }
		if led.length == 1
			next if point_within_perimeter?(ptmid)
		end
		lerase.push face
	end
	gent.erase_entities lerase if lerase.length > 0

	#Erasing the remaining lonely edges
	lerase = gent.grep(Sketchup::Edge).find_all { |e| e.faces.length == 0 }
	gent.erase_entities lerase if lerase.length > 0
	
	#Setting property for the outer edges
	@outer_edges = gent.grep(Sketchup::Edge).find_all { |e| e.faces.length == 1 }
	@outer_edges.each { |e| e.smooth = e.soft = false }	
	@outer_edges.each { |e| e.material = 'purple' }	
end

#GEOMETRY: Creating a subgroup with the original cloud points
def geometry_points
	return unless @group_terrain
	
	#Deleting the existing subgroup if any
	if @group_points
		@group_points.erase!
		@group_points = nil
	end
	return unless @points_enabled
	
	#Creating the subgroup
	gt_ent = @group_terrain.entities
	@group_points = gt_ent.add_group
	gent = @group_points.entities
	
	#Creating the cloud points
	gline = (@points_style == :line)
	@cloud_points_ori.each do |pt|
		ptxy = Geom::Point3d.new pt.x, pt.y, 0
		gent.add_cpoint @tr_draw * pt
		gent.add_cline(@tr_draw * ptxy, @tr_draw * pt) if gline
	end	
end

#GEOMETRY: Generate the isocontours
def geometry_iso_contours(progression=false)
	tx = (@iso_delta) ? Sketchup.format_length(@iso_delta) : "nil"
	return unless @group_terrain
	
	#Deleting the existing group if any
	if @group_iso
		@group_iso.erase!
		@group_iso = nil
	end
	
	#Nothing to do if iso contours are not specified
	return unless @iso_enabled && @iso_delta && @iso_delta > 0.001
	
	#Diagonal points of the terrain
	delta = @iso_delta
	vec = @ptmin.vector_to @ptmax
	d = vec.length * 0.1
	ptmin = @ptmin.offset vec, -d
	ptmax = @ptmax.offset vec, d

	pt1 = Geom::Point3d.new ptmax.x, ptmin.y
	pt3 = Geom::Point3d.new ptmin.x, ptmax.y
	rect = [ptmin, pt1, ptmax, pt3]
		
	#Creating the subgroup for iso-contours
	gt_ent = @group_terrain.entities
	@group_iso = gt_ent.add_group
	giso_ent = @group_iso.entities
	
	#Computing the Z limits
	zref = @pt_ref.z
	zmin = ptmin.z
	zmax = ptmax.z
	z = (zmin >= zref) ? zref : zref - ((zref - zmin) / delta).ceil * delta
	ntot = ((zmax - z) / delta).ceil
	
	#Creating the progression bar if required
	delay = 0.5
	vgbar = nil
	if progression
		hsh = { :delay => delay }
		vgbar = Traductor::VisualProgressBar.new T6[:VGBAR_IsoContours], hsh
		vgbar.start
	end
	
	#Creating the iso-contours by intersection
	t0 = Time.now
	n = 0
	while z < zmax
		if vgbar && (Time.now - t0) > delay
			vgbar.progression(1.0 * n / ntot)
			t0 = Time.now
		end	
		
		#Creating the planes
		group_plane = @group_terrain.entities.add_group
		gent = group_plane.entities
		rect2 = rect.collect { |pt| pt2 = pt.clone ; pt2.z = z ; pt2 }
		rect2 = rect2.collect { |pt| @tr_draw * pt }
		face = gent.add_face rect2
		z += delta
		
		#Intersection
		edges = gt_ent.intersect_with false, @tr_id, giso_ent, @tr_id, false, group_plane
		edges.each { |e| e.material = @color_iso_contour } if @use_color_iso
		
		#Erasing the plane group
		group_plane.erase!
		n += 1
	end	
	vgbar.stop if vgbar
end

#GEOMETRY: Generate the isocontours
def geometry_skirt
	return unless @group_terrain
	gt_ent = @group_terrain.entities

	#Deleting the existing group if any
	if @group_skirt
		@group_skirt.erase!
		@group_skirt = nil
	end
	
	#Nothing to do if iso contours are not specified
	delta = @skirt_delta
	return unless @skirt_enabled && delta && @zmin != @zmax

	#Subgroup for the skirt
	@group_skirt = gt_ent.add_group
	gent = @group_skirt.entities
	
	#Computing the base plane
	dz = (@zmax - @zmin) * delta * 1.0 / 100
	ptbase = @ptmin.clone
	ptbase.z = 0 unless ptbase.z <= 0
	ptbase.z -= dz
	base_plane = [@tr_draw * ptbase, G6.transform_vector(Z_AXIS, @tr_draw)]
	
	#Generating the faces
	mesh = Geom::PolygonMesh.new
	@outer_edges.each do |edge|
		pt1 = edge.start.position
		pt2 = edge.end.position
		pt1, pt2 = [pt2, pt1] unless edge.reversed_in?(edge.faces[0])
		pt3 = pt2.project_to_plane base_plane
		pt4 = pt1.project_to_plane base_plane
		next if pt1 == pt4 && pt2 == pt3
		if pt1 == pt4
			lpt = [pt1, pt2, pt3]
		elsif pt2 == pt3	
			lpt = [pt1, pt2, pt4]
		else
			lpt = [pt1, pt2, pt3, pt4]
		end	
		mesh.add_polygon lpt
	end	
	gent.fill_from_mesh mesh, true, 0
	
	#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 = e.smooth = true if angle < 20.degrees
	end	
	
	#Closing the skirt at the bottom
	trinv = @tr_draw.inverse
	zbase = ptbase.z
	edges = gent.find_all { |e| e.instance_of?(Sketchup::Edge) && (trinv * e.start.position).z = zbase }
	edges.each do |e| 
		e.find_faces if e.faces.length == 1
	end	
	
	#Colorizing the skirt faces
	gent.grep(Sketchup::Face).each { |f| f.material = @color_skirt } if @use_color_skirt
end
	
#GEOMETRY: Compute the List of groups forming the terrain
def geometry_valid_group
	@lst_valid_groups = [@group_terrain]
	@lst_valid_groups.push @group_ori if @group_ori
	@lst_valid_groups.push @group_iso if @group_iso
	@lst_valid_groups.push @group_skirt if @group_skirt
	@lst_valid_groups.push @group_points if @group_points
	@lst_valid_groups
end	

#GEOMETRY: Acknowledge erasing of geometry
def geometry_erased
	@group_terrain = @group_iso = @group_skirt = @group_points = nil
	@geometry_generated = false
end	

#GEOMETRY: Update the generated model when colors have changed
def geometry_change_colors
	init_colors
	if @group_terrain && @group_terrain.valid?
		color = (@use_color_terrain) ? @color_terrain : nil
		@group_terrain.entities.grep(Sketchup::Face).each { |f| f.material = color }
	end	
	if @group_skirt && @group_skirt.valid?
		color = (@use_color_skirt) ? @color_skirt : nil
		@group_skirt.entities.grep(Sketchup::Face).each { |f| f.material = color }
	end	
	if @group_iso && @group_iso.valid?
		color = (@use_color_iso) ? @color_iso : nil
		@group_iso.entities.grep(Sketchup::Edge).each { |e| e.material = color }
	end	
end

#---------------------------------------------------------------------------------------------
# PICKING: Methods for interactive picking
#---------------------------------------------------------------------------------------------

#PICKING: Manage picking of sharp edge and cloud points
def info_picking ; @info_picking ; end
def element_under_mouse(flags, x, y, view)
	@info_picking = nil
	return nil unless @geometry_generated

	#List of valid groups
	geometry_valid_group
	
	#Picking elements
	@ph.do_pick x, y

	#Analysing the elements picked
	lst_anchor = []
	nelt = @ph.count
	for i in 0..nelt
		path = @ph.path_at(i)
		break unless path && path.length > 0
		elt = path.last	
		next if elt.class == Sketchup::SectionPlane && nelt > 1
		comp = (path.length == 1) ? nil : path[-2]
		next unless @lst_valid_groups.include?(comp)
		lst_anchor.push [i, elt, comp, @ph.transformation_at(i)]
	end

	#No element picked
	return nil if lst_anchor.empty?
	
	#Analyzing the elements cloud point and iso_contour
	lst_anchor.each do |a|
		i, elt, comp, tr = a
		if elt.instance_of?(Sketchup::ConstructionPoint) && (comp == @group_ori || comp == @group_points)
			@info_picking = [:cloud_point, "#{Sketchup.format_length geometry_altitude(tr * elt.position)}"]
		elsif elt.instance_of?(Sketchup::Edge) && comp == @group_iso
			@info_picking = [:iso, "#{Sketchup.format_length geometry_altitude(tr.inverse * elt.start.position)}"]
		end
	end
	return if @info_picking
	
	#Analyzing sharp edges
	hfaces = {}
	lst_anchor.each do |a|
		i, elt, comp, tr = a
		if comp == @group_terrain && elt.instance_of?(Sketchup::Face)
			hfaces[elt.entityID] = elt
		end
	end
	return if hfaces.length != 2
	face1, face2 = hfaces.values
	ledges = face1.edges & face2.edges
	return unless ledges.length == 1
	angle = face1.normal.angle_between(face2.normal).radians
	return unless angle > @deviation_max
	
	if angle > @deviation_max * @fac_sharp_borders2
		edge = ledges[0]
		if edge.length < @distmin
			symb = :tiny_very_sharp
		else	
			symb = :very_sharp
		end	
	else 
		symb = :sharp
	end	
	
	@info_picking = [symb, sprintf("%2.1f°", angle)]
end

#GEOMETRY: Compute the altitude of a point for display
def geometry_altitude(pt)
	pt0 = @tr_draw_inv * pt
	pt0.z - @pt_ref.z
end

end	#class PointCloudTriangulation

end	#End Module F6_TopoShaper
