=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Copyright © 2009 Fredo6 - Designed and written April 2009 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			:   RoundCorner_Algo.rb
# Original Date	:   30 April 2009 - version 2.0
# Description	:   Algorithm for RoundCorner
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module RoundCorner

#---------------------------------------------------------------------------------------------------------------------------
# Common Structure to represent the model	
#---------------------------------------------------------------------------------------------------------------------------

RDC_Edge = Struct.new :edge, :entityID, :corners, :lfaces, :loffset, :lfactors, :vec,
                             :ptmid, :lvecins, :profile, :length, :pairs, :facemain, :nsection,
							 :angle, :cos, :cosinv, :parallels, :aligned, :sharp_sections,
							 :catenas, :cross_sections, :golden, :round_profile, :vmesh, :vborders

RDC_Corner = Struct.new :vertex, :entityID, :leds, :pairs, :round_pair, :gold_ed, :convex, :vmesh,
                                 :sharp_inter

RDC_Pair = Struct.new :vd, :ledges, :lfaces, :plane, :ptcross, :alone, :edge_terms, :leds, :cross_plane,
                           :convex, :monoface, :rounding, :mode

RDC_Catena = Struct.new :chain, :leds, :nbsmall
								  

#---------------------------------------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------------------------------
# Class RoundCorneAlgo: implement the algorithm for rounding edges and corners
#---------------------------------------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------------------------------

class RoundCornerAlgo

#Initialization 
def initialize(*hargs)
	#initialization
	@model = Sketchup.active_model
	@selection = @model.selection
	@view = @model.active_view

	@hsh_profile_pts = {}
	init_colors
	reset_global
	
	@trace_on = false
	
	@offset = nil
	reset_history
	
	@golden_axis = Z_AXIS
	@offset = 1000.cm
	@cap_offset = 4.5
	
	#@profile_type = ['P', 0.5]
	@profile_type = ['C', 6]
	@mode_profile = 1
	#@mode_profile = 0

	@mode_sharp = false
	@edge_filter = 'P'
	change_strict_offset false
		
	#Initialization message
	@msg_abort = T6[:MSG_Abort]
	@msg_abort_leave = T6[:MSG_AbortLeave]
	@label_wait = T6[:LABEL_Wait]
	@label_finishing = T6[:LABEL_Finishing]

	#Parsing arguments
	hargs.each do |arg|	
		arg.each { |key, value| parse_args(key, value) } if arg.class == Hash	
	end	
	
	#Post control on parameters
	verify_profile @profile_type, @num_seg
	@mode_profile = 0 if @mode_profile > 0 && @mode_sharp
end

def reset_history
	@hash_edges = {}
	@hash_edges_extra = {}
	@hsh_edge_info = {}
	@hsh_vertex_info = {}
end

#Parse arguments
def parse_args(key, value)
	skey = key.to_s
	case skey
	when /sharp/i
		@mode_sharp = value
	when /strict_offset/i
		@strict_offset = value
	when /mode_rounding/i
		@mode_rounding = value
	when /profile_type/i
		@profile_type = value
	when /prop_filter/i
		@edge_filter = value
	when /prop_borders/i
		@prop_borders = value
	when /prop_rounds/i
		@prop_rounds = value
	when /offset/i
		@offset = value if value
	when /num_seg/i
		@num_seg = value if value
	when /cursor_proc/i
		@cursor_proc = value if value
	when /golden_axis/i
		if value.class == String
			case value
				when /X/i ; value = X_AXIS
				when /Y/i ; value = Y_AXIS
				else ; value = Z_AXIS
			end
		end
		@golden_axis = value if value
		
	end
end

#Define the end procedure of the caller
def define_end_proc(&proc)
	@end_proc = proc
end

#Initialize colors
def init_colors
	@color_edges = MYDEFPARAM[:DEFAULT_Color_Edges]
	@color_pivots = MYDEFPARAM[:DEFAULT_Color_Pivots]
	@color_convex = MYDEFPARAM[:DEFAULT_Color_PivotsConvex]
	@color_rounds = MYDEFPARAM[:DEFAULT_Color_PivotsRound]
	@color_borders = MYDEFPARAM[:DEFAULT_Color_Borders]
	@color_implicit = MYDEFPARAM[:DEFAULT_Color_Implicit]
	@color_excluded = MYDEFPARAM[:DEFAULT_Color_Excluded]
end

#Initialze the representation model
def reset_global
	@hsh_ed = {}
	@hsh_corners = {}
	@lpt_lines_to_draw = nil
	@lpt_borders = []
	@lst_catenas = []
	@lst_mark_points = []
	@hsh_error_vertex = {}
	@running = false
end

#Clean up the selection
def unselect_all
	reset_global
	reset_history
end

#Restart from scratch
def restart
	recalculate
end

#---------------------------------------------------------------------------------------------------------------------------
# Methods to modify the parameters
#---------------------------------------------------------------------------------------------------------------------------

#Modify the global offset value
def change_offset(offset)
	return if offset == @offset
	refresh_for_view
	@offset = offset
	
	recalculate_based_on_offset
end

#Modify the profile used
def change_prop_filter(prop_filter)
	@edge_filter = prop_filter
end

#Modify the profile used
def change_profile(profile_type=nil)
	return if profile_type == @profile_type
	@profile_type = profile_type if profile_type
	verify_profile @profile_type
end

#Change the option for strict offet
def change_strict_offset(strict_offset)
	return @strict_offset if @strict_offset == strict_offset 
	#return(@strict_offset = false) if strict_offset && @mode_sharp
	@strict_offset = strict_offset
	recalculate
	@strict_offset
end

#Change the option for strict offet
def change_rounding(mode_rounding)
	return @mode_rounding if @mode_rounding == mode_rounding 
	@mode_rounding = mode_rounding
	recalculate
	@mode_rounding
end

#Change the golden axis
def change_golden_axis(axis)
	return @golden_axis if @golden_axis == axis 
	@golden_axis = axis
	refresh_for_view
	#recalculate_based_on_offset
	recalculate
	@golden_axis
end

#Change the number of segments for standard profiles
def change_numseg(numseg, set_only=false)
	return @num_seg if @num_seg_lock || numseg == nil || numseg <= 0
	@profile_type[1] = numseg
	verify_profile @profile_type, numseg
	recalculate_based_on_offset if set_only
	@num_seg
end

#Change all parameters at once
def refresh_all_parameters(*hargs)
	hargs.each do |arg|	
		arg.each { |key, value| parse_args(key, value) } if arg.class == Hash	
	end	
	refresh_for_view
	recalculate
end

#Change all parameters at once (called from dialog box)
def change_all_parameters(*hargs)
	hargs.each do |arg|	
		arg = parse_all_dlg_parameters arg
		arg.each { |key, value| parse_args(key, value) } if arg.class == Hash	
	end	
	change_numseg @num_seg, true
	refresh_for_view
	recalculate
end

def get_all_dlg_parameters(hparam=nil)
	hparam = {} unless hparam
	
	hparam["offset"] = @offset.to_l
	hparam["num_seg"] = @num_seg
	case @golden_axis
		when X_AXIS ; hparam["golden_axis"] = 'X'
		when Y_AXIS ; hparam["golden_axis"] = 'Y'
		else ; hparam["golden_axis"] = 'Z'
	end
	hparam["strict_offset"] = ((@strict_offset) ? 'Y' : 'N')
	hparam["mode_rounding"] = ((@mode_rounding) ? 'Y' : 'N')
	
	hparam
end

def parse_all_dlg_parameters(hparam)	
	hsh = {}
	hsh["offset"] = hparam["offset"]
	hsh["num_seg"] = hparam["num_seg"]
	case hparam["golden_axis"]
		when 'X' ; hsh["golden_axis"] = X_AXIS
		when 'Y' ; hsh["golden_axis"] = Y_AXIS
		else ; hsh["golden_axis"] = Z_AXIS
	end
	hsh["strict_offset"] = (hparam["strict_offset"] == 'Y')
	hsh["mode_rounding"] = (hparam["mode_rounding"] == 'Y')
	
	hsh
end

#---------------------------------------------------------------------------------------------------------------------------
# Method for interactive edition
#---------------------------------------------------------------------------------------------------------------------------

#check if an edge is candidate for a selection
def accept_edge?(edge)
	lfaces = edge.faces
	
	#Edge must have 2 faces
	unless lfaces.length == 2
		@hsh_edge_info[edge.entityID] = ['2', edge]
		return false
	end	
	
	#Edge must not be coplanar
	face1 = lfaces[0]
	face2 = lfaces[1]
	if face1.normal.parallel?(face2.normal)
		@hsh_edge_info[edge.entityID] = ['P', edge]
		return false
	end	
	
	#Filter of edge properties
	status = G6.edge_filter?(edge, @edge_filter)
	unless status
		@hsh_edge_info[edge.entityID] = ['F', edge]
		return false
	end	
	
	@hsh_edge_info[edge.entityID] = nil
	true
end

#Return the code for invalid edge
def invalid_reason(edge)
	return '' unless edge
	ll = @hsh_edge_info[edge.entityID]
	(ll) ? ll[0] : ''
end

#Verify each vertex to check if edges should be added
def verify_all_vertices
	#Building the list of edges selected at vertex
	@hash_edges_extra.each do |key, edge|
		@hash_edges.delete edge.entityID
	end
	@hash_edges_extra = {}
	@hsh_vertex_edges = {}
	@hash_edges.delete_if { |key, edge| !edge.valid? }
	
	@hash_edges.each do |key, edge|
		register_vertex edge.start, edge
		register_vertex edge.end, edge
	end
	
	#Checking of this is the right number
	@hash_edges.each do |key, edge|
	####@hash_edges.values do |edge|
		verify_vertex edge.start
		verify_vertex edge.end
	end	
end

def register_vertex(vertex, edge)
	ledges = @hsh_vertex_edges[vertex.entityID]	
	ledges = @hsh_vertex_edges[vertex.entityID] = [] unless ledges
	ledges.push edge unless ledges.include?(edge)
end

#Verify one vertex for additional edges to include
def verify_vertex(vertex)
	#computing the list of valid edges
	ledges = @hsh_vertex_info[vertex.entityID]
	unless ledges
		ledges = []
		vertex.edges.each do |edge|
			ledges.push edge unless edge.faces.length != 2 || edge.faces[0].normal.parallel?(edge.faces[1].normal)
		end
		@hsh_vertex_info[vertex.entityID] = ledges
	end	
	
	#Checking if same number as selected
	lsel = @hsh_vertex_edges[vertex.entityID]
	nsel = lsel.length
	return unless nsel >= 3 && nsel != ledges.length
	
	#Adding extra edges
	ledges.each do |edge|
		next if @hash_edges[edge.entityID]
		@hash_edges[edge.entityID] = edge
		register_vertex edge.start, edge
		register_vertex edge.end, edge
		@hash_edges_extra[edge.entityID] = edge
	end
	
end

#Analyze the initial selection
def analyze_selection(entities)
	entities.each do |e|
		if e.class == Sketchup::Edge
			@hash_edges[e.entityID] = e
		elsif e.class == Sketchup::Face
			e.edges.each { |edge| @hash_edges[edge.entityID] = edge }
		end
	end	
	verify_all_vertices
	recalculate
end

#Add an element to the model
def add_remove_entity(entity)
	entity = [entity] unless entity.class == Array
	ledges = []
	entity.each do |e|
		ledges += edges_of_entity e
	end
	ledges.uniq!
	
	#Adding or deleting edges
	ladd = []
	ldel = []
	ledges.each do |edge|
		id = edge.entityID
		if @hash_edges[id] && !@hash_edges_extra[id]
			ldel.push id
		else
			ladd.push edge
		end	
	end	
	if ladd.length > 0
		ladd.each do |edge| 
			@hash_edges[edge.entityID] = edge
			@hash_edges_extra.delete edge.entityID
		end	
	elsif ldel.length > 0
		ldel.each { |id| @hash_edges.delete id }
	end	
	
	#Verifying the resulting list of edges
	verify_all_vertices
	
	#Recalculating the whole model
	recalculate
end

#Add an element to the model
def edges_of_entity(e)
	ledges = []
	return ledges unless e
	if e.class == Sketchup::Edge
		ledges.push e if accept_edge?(e)
	elsif e.class == Sketchup::Vertex
		e.edges.each { |edge| ledges.push edge if accept_edge?(edge) }
	elsif e.class == Sketchup::Face
		e.edges.each { |edge| ledges.push edge if accept_edge?(edge) }
	end	
	ledges
end

#Recalculation of the model after adding or removing edges
def recalculate
	#resetting the model
	reset_global
	return unless @hash_edges.length > 0
	
	#creating the model structure
	@hash_edges.each do |key, edge|
		integrate_edge edge
	end
	
	#Compute the pairs at vertex
	@hsh_corners.each do |key, vd|
		compute_pairs_at_vertex vd
	end
		
	#calculate the catenas
	catena_compute_all
	catena_offset_all if @strict_offset
	
	#Calculate the borders, golden edges and roundings
	recalculate_based_on_offset
end
	
def recalculate_based_on_offset
	@lpt_borders = []
	@hsh_ed.each do |key, ed|
		ed.nsection = nil
		ed.pairs.each { |pair| pair.ptcross = nil}
		compute_borders ed
	end		

	#Scan the corners to determine the roundings
	corner_scan_all
	
	#Compute the faces orientation -The tricky computation is when the number of segments is odd
	compute_face_orientation
	
	#Compute alone pairs in case of  triangulation
	evaluate_pair_triangulated
	
	#Compute the Roundings
	@hsh_corners.each { |key, vd| rounding_compute vd }
	@hsh_ed.each { |key, ed| compute_borders_for_display ed }		
end	

#Evaluate and treat pairs at triangulated corners
def evaluate_pair_triangulated
	@lst_triangulated = []
	lalone = @hsh_corners.values.find_all { |vd| vd.leds.length == 2 && vd.pairs.length >= 3 }
	
	lalone.each_with_index do |vd, i|
		ed1 = vd.leds[0]
		ed2 = vd.leds[1]
		pairs1 = vd.pairs.find_all { |pair| pair.alone && pair.leds[0] == ed1 }
		pairs2 = vd.pairs.find_all { |pair| pair.alone && pair.leds[0] == ed2 }
		next unless pairs1 && pairs2
		ptcross10 = pairs1[0].ptcross
		ptcross11 = (pairs1[1]) ? pairs1[1].ptcross : nil
		ptcross20 = pairs2[0].ptcross
		ptcross21 = (pairs2[1]) ? pairs2[1].ptcross : nil
		unless ptcross21 && ptcross10.distance(ptcross21) < ptcross10.distance(ptcross20)
			seg1 = [ptcross10, ptcross20]
		else
			seg1 = [ptcross10, ptcross21]
		end
		seg2 = nil
		if ptcross11 	
			unless ptcross21 && ptcross11.distance(ptcross21) < ptcross11.distance(ptcross20)
				seg2 = [ptcross11, ptcross20]
			else
				seg2 = [ptcross11, ptcross21]
			end
		end
		
		@lpt_borders += seg1
		@lpt_borders += seg2 if seg2
		@lst_triangulated.push [vd, ed1, ed2, seg1, seg2]
	end	
end

#---------------------------------------------------------------------------------------------------------------------------
# Methods to communicate with the UI tool
#---------------------------------------------------------------------------------------------------------------------------
	
def get_offset ; @offset ; end
def get_num_seg ; @num_seg ; end
def get_golden_axis ; @golden_axis ; end
def get_strict_offset ; @strict_offset ; end
def get_mode_rounding ; @mode_rounding ; end
def get_num_seg_lock ; @num_seg_lock ; end	
def get_nb_edges ; @hsh_ed.length ; end
def get_nb_corners ; @hsh_corners.length ; end
def get_error_vertex ; @hsh_error_vertex ; end

#Detremine if the entity is already selected
def entity_selected?(entity)
	return false unless entity.valid?
	status = true
	if entity.class == Sketchup::Edge
		status = false unless @hash_edges[entity.entityID] && !@hash_edges_extra[entity.entityID]
	elsif entity.class == Sketchup::Vertex || entity.class == Sketchup::Face
		entity.edges.each do |e|
			if accept_edge?(e) && !@hash_edges[e.entityID]
				status = false
				break
			end
		end
	end
	status	
end

def set_prop_borders(prop)
	@prop_borders = prop
end

def set_prop_rounds(prop)
	@prop_rounds = prop
end

#reset the lines to draw in the main tool
def refresh_for_view
	@lpt_lines_to_draw = nil
end

#Get the encoded list of lines to be drawn
def get_all_lines_to_draw
	return @lpt_lines_to_draw if @lpt_lines_to_draw
	lnormal = []
	lpivot_regular = []
	lpivot_convex = []
	lpivot_round = []
	lextras = []
	@hsh_ed.each do |key, ed|
		if @hash_edges_extra[ed.edge.entityID]
			ll = lextras
		elsif ed.golden == -1
			ll = lpivot_convex
		elsif ed.round_profile
			ll = lpivot_round
		elsif ed.corners[0].gold_ed == ed || ed.corners[1].gold_ed == ed
			ll = lpivot_regular
		else
			ll = lnormal
		end	
		edge = ed.edge
		ll.push edge.start.position, edge.end.position
	end
	@lpt_lines_to_draw = []
	@lpt_lines_to_draw.push [lextras, @color_implicit, 2, ''] if lextras.length > 0
	@lpt_lines_to_draw.push [lnormal, @color_edges, 2, ''] if lnormal.length > 0
	@lpt_lines_to_draw.push [lpivot_regular, @color_pivots, 4, ''] if lpivot_regular.length > 0
	@lpt_lines_to_draw.push [lpivot_convex, @color_convex, 4, ''] if lpivot_convex.length > 0
	@lpt_lines_to_draw.push [lpivot_round, @color_rounds, 3, ''] if lpivot_round.length > 0
	@lpt_lines_to_draw.push [@lpt_borders, @color_borders, 2, ''] if @lpt_borders && @lpt_borders.length > 0
	
	@lpt_lines_to_draw
end	
		
#---------------------------------------------------------------------------------------------------------------------------
# Compute the model representation
#---------------------------------------------------------------------------------------------------------------------------

#Integrate an edge into the model selection
def integrate_edge(edge)
	return nil unless accept_edge?(edge)
	entityID = edge.entityID
	ed = @hsh_ed[entityID]
	return ed if ed
	
	#Creating the Edge structure
	ed = RoundCorner::RDC_Edge.new
	@hsh_ed[entityID] = ed
	ed.edge = edge
	ed.entityID = entityID
	face1 = edge.faces[0]
	face2 = edge.faces[1]		
	ed.lfaces = [face1, face2]

	ed.lvecins = [G6.normal_in_to_edge(edge, face1), G6.normal_in_to_edge(edge, face2)]
	ed.angle = 0.5 * Math::PI - ed.lvecins[0].angle_between(ed.lvecins[1])
	ed.cos = Math.cos ed.angle
	ed.cosinv = 1.0 / ed.cos
	####puts "SET 1" if ed.cosinv > @cap_offset
	ed.cosinv = @cap_offset if ed.cosinv > @cap_offset
	ed.lfactors = [ed.cosinv, ed.cosinv]

	vxbeg = edge.start
	vxend = edge.end
	ed.corners = []
	ed.catenas = []
	ed.pairs = []
	ed.parallels = []
	ed.cross_sections = [[], []]
	ed.sharp_sections = [[], []]
	ed.golden = 100
	ed.aligned = false
	ed.vec = vxbeg.position.vector_to(vxend.position).normalize
	ed.length = edge.length
	ed.round_profile = false
	ed.ptmid = Geom.linear_combination 0.5, vxbeg.position, 0.5, vxend.position
	ed.parallels = ed.lvecins.collect { |vecin| [ed.ptmid.offset(vecin, 1.0), ed.vec] }
	@offset = vxbeg.position.distance(vxend.position) * 0.1 unless @offset
	ed.loffset = [nil, nil]
	
	#Analyzing the extremities of the edge
	ed.corners[0] = create_corner edge.start, ed	
	ed.corners[1] = create_corner edge.end, ed	
	
	#Reset the list for display
	@lpt_lines_to_draw = nil
	
	ed
end

#Create a Corner structure
def create_corner(vertex, ed)
	entityID = vertex.entityID
	vd = @hsh_corners[entityID]
	
	#Registering the corner
	unless vd
		vd = RoundCorner::RDC_Corner.new
		@hsh_corners[entityID] = vd
		vd.entityID = entityID
		vd.vertex = vertex
		vd.leds = []
		vd.pairs = []
		vd.gold_ed = nil
		vd.sharp_inter = []
	end	
	unless vd.leds.include?(ed)
		vd.leds.push ed
	end
	
	vd
end

#---------------------------------------------------------------------------------------------------------------------------
# Managing pairs
#---------------------------------------------------------------------------------------------------------------------------

#Create a pair and store it at the corresponding edges
def create_pair(vd, ed1, face1, ed2=nil, face2=nil)
	#creating the structure
	edge1 = ed1.edge
	edge2 = (ed2) ? ed2.edge : nil
	pair = RoundCorner::RDC_Pair.new
	pair.vd = vd
	pair.ledges = [edge1, edge2]
	pair.leds = [ed1, ed2]
	pair.lfaces = [face1, face2]
	pair.ptcross = nil
	pair.edge_terms = []
	pair.monoface = true
	pair.convex = false
	pair.alone = (edge2) ? false : true
	vd.pairs.push pair
	vertex = vd.vertex
	
	#Populating for Edge 1
	iface1 = (face1 == ed1.lfaces[0]) ? 0 : 1
	ivtx = (vertex == edge1.start) ? 0 : 1
	ed1.pairs[iface1 + 2 * ivtx] = pair
	
	#Populating for Edge 2 if any
	if edge2
		iface2 = (face2 == ed2.lfaces[0]) ? 0 : 1
		ivtx = (vertex == edge2.start) ? 0 : 1
		ed2.pairs[iface2 + 2 * ivtx] = pair
		pair.monoface = ((face1 == face2) || (face1.normal.parallel?(face2.normal)))
		if pair.monoface
			pair.convex = G6.convex_at_vertex(vertex, face1, ed1.edge, ed2.edge, face2)
			pair.vd.convex = true if pair.convex
		end	
	end
	
	#Computing the planes for cross points - Special computation for termination corners
	if pair.alone
		compute_edge_terms pair, pair.ledges[0], pair.lfaces[0], pair.vd.vertex
	end	
	
	#returning the structure
	pair
end

#Find the matching edges at a vertex
def compute_pairs_at_vertex(vd)
	nb_edge = vd.leds.length
	
	#Only one edge at vertex
	if nb_edge == 1
		ed = vd.leds[0]
		create_pair vd, ed, ed.lfaces[0]
		create_pair vd, ed, ed.lfaces[1]
		
	#Special treatment when only 2 edges	
	elsif nb_edge == 2
		check_pair_2 vd, vd.leds[0], vd.leds[1]
	
	#3 edges or more - finding edges sharing a common face
	else
		n = nb_edge - 1
		for i in 0..n-1
			for j in i+1..n
				check_pair_N vd, vd.leds[i], vd.leds[j], vd.leds
			end	
		end
	end
end

#Find the common faces (or coplanar) to 2 edges when there are more than 2 at vertex
def check_pair_2(vd, ed1, ed2)
	edge1 = ed1.edge
	edge2 = ed2.edge
	faces1 = edge1.faces.collect { |f| f }
	faces2 = edge2.faces.collect { |f| f }

	#Finding faces that are on same plane
	edge1.faces.each do |f1|
		edge2.faces.each do |f2|
			if f1 == f2 || f1.normal.parallel?(f2.normal)
				create_pair vd, ed1, f1, ed2, f2
				faces1 -= [f1]
				faces2 -= [f2]
			end	
		end				
	end
	
	#Finding faces with common edges
	lf1 = []
	lf2 = []
	faces1.each do |f1|
		faces2.each do |f2|
			linter = f1.edges & f2.edges
			if linter.length > 0 
				e = linter[0]
				unless (G6.convex_at_vertex(vd.vertex, f1, edge1, e) || G6.convex_at_vertex(vd.vertex, f2, edge2, e))
					create_pair vd, ed1, f1, ed2, f2
					lf1 += [f1]
					lf2 += [f2]
				end	
			end	
		end
	end

	#Creating alone pair if there remain unaffected faces
	faces1 -= lf1
	faces2 -= lf2
	faces1.each { |f1| create_pair vd, ed1, f1 }
	faces2.each { |f2| create_pair vd, ed2, f2 }
end

#Find the common faces (or coplanar) to 2 edges when there are more than 2 at vertex
def check_pair_N(vd, ed1, ed2, leds)
	edge1 = ed1.edge
	edge2 = ed2.edge
	return if edge1 == edge2
	faces1 = edge1.faces
	faces2 = edge2.faces
	lfaces = faces1 & faces2
	
	#Common face found	
	if lfaces.length >= 1
		return create_pair(vd, ed1, lfaces[0], ed2, lfaces[0])
	end	
	
	#Finding faces that are on same plane
	edge1.faces.each do |f1|
		edge2.faces.each do |f2|
			if f1.normal.parallel?(f2.normal)
				return create_pair(vd, ed1, f1, ed2, f2)
			end	
		end				
	end
	
	#Check common edges
	faces1.each do |f1|
		faces2.each do |f2|
			linter = f1.edges & f2.edges
			if linter.length > 0 && !leds.find { |ed| ed.edge == linter[0] }
				return create_pair(vd, ed1, f1, ed2, f2)
			end	
		end
	end
end

#Compute or recompute the borders
def compute_borders(ed)
	pairs = ed.pairs
	for i in 0..3
		compute_pair_ptcross pairs[i] if pairs[i]
	end	
end	

#Compute the borders for display, including the rounding
def compute_borders_for_display(ed)
	pairs = ed.pairs
	
	lseg = []
	lroundings = []
	[0, 2, 1, 3].each do |i|
		pair = pairs[i]
		lpt = pair.rounding
		if lpt && lpt.length > 0
			pt = (lpt.first.on_line?([pair.ptcross, ed.vec])) ? lpt.first : lpt.last	
			lroundings.push lpt
		else
			pt = pair.ptcross
		end	
		lseg.push pt
	end
	@lpt_borders += lseg
	
	lroundings.each do |lpt|
		for i in 0..lpt.length-2
			@lpt_borders.push lpt[i], lpt[i+1]
		end	
	end
end

# Compute and return the vector representing the border 
def compute_edge_terms(pair, edge, face, vertex)
	normal = face.normal
	vertex.edges.each do |e|
		next if e == edge
		e.faces.each do |f|
			if f == face || f.normal.parallel?(normal)
				pair.edge_terms.push e
			end
		end	
	end
end

#Compute the cross points for the pair
def compute_pair_ptcross(pair)
	return if pair.ptcross
	
	line1 = line_border pair, 0
	if pair.alone
		pair.ptcross = find_intersect_edge_term pair, line1
	else
		line2 = line_border pair, 1
		if line1[1].parallel?(line2[1])
			pair.ptcross = Geom.intersect_line_plane line1, [pair.vd.vertex.position, line1[1]]
		else	
			pair.ptcross = Geom.intersect_line_line line1, line2
			unless pair.ptcross
				signal_error_vd pair.vd 
				lpt = Geom.closest_points line1, line2
				pair.ptcross = lpt[0]
			end	
		end	
	end	
	
	ptv = pair.vd.vertex.position
	vec = ptv.vector_to pair.ptcross
end

#Find the best stop point for a standalone edge pair
def find_intersect_edge_term(pair, line)
	ed = pair.leds[0]
	lptinter = []
	pair.edge_terms.each do |edge|
		pt = G6.intersect_edge_line edge, line
		lptinter.push pt if pt
	end	
	
	#No intersection found. Just take the ortogonal plane
	if lptinter.length == 0
		######puts "ORTHO"
		return Geom.intersect_line_plane(line, [pair.vd.vertex.position, ed.vec])
	end
	
	#Sorting the intersection point and taking the farthest
	ptmid = ed.ptmid
	lptinter.sort! { |pt1, pt2| ptmid.distance(pt1) <=> ptmid.distance(pt2) }
	lptinter.last	
end

#Compute the line defining a border on a face
def line_border(pair, iedge)
	edge = pair.ledges[iedge]
	ed = @hsh_ed[edge.entityID]
	face = pair.lfaces[iedge]
	iface = (face == ed.lfaces[0]) ? 0 : 1
	offset = @offset * ed.lfactors[iface]
	vecin = G6.normal_in_to_edge edge, face
	pt = ed.ptmid.offset vecin, offset
	[pt, ed.vec]
end

#Compute the orthogonal profile for an edge
def compute_profile_edge(ed)
	return ed.nsection if ed.nsection
	offset1 = @offset * ed.lfactors[0]
	offset2 = @offset * ed.lfactors[1]
	vec1 = ed.lvecins[0]
	vec2 = ed.lvecins[1]
	ptcenter = ed.ptmid
	profile = (ed.round_profile) ? ['C', @num_seg] : @profile_type
	section = profile_compute_by_offset profile, ptcenter, vec1, offset1, vec2, offset2
	section = section.reverse unless section[0].on_plane?(ed.facemain.plane)
	ed.nsection = section
	section
end

#Compute the section for an edge at corners with at least 3 edges
def compute_sections_multi(ed, icorner)
	vd = ed.corners[icorner]
	vpos = vd.vertex.position
	iA = (ed.facemain == ed.lfaces[0]) ? 0 : 1
	iB = 1 - iA
	pairA = ed.pairs[2 * icorner + iA]
	pairB = ed.pairs[2 * icorner + iB]

	ptA = pairA.ptcross
	ptB = pairB.ptcross

	profile = (ed.round_profile) ? ['C', @num_seg] : @profile_type
	
	#Termination with one or only 2 edges at vertex
	if (vd.leds.length <= 1)
		vecA = vpos.vector_to ptA
		vecB = vpos.vector_to ptB
		offsetA = vpos.distance ptA
		offsetB = vpos.distance ptB
		section = profile_compute_by_offset profile, vpos, vecA, offsetA, vecB, offsetB

	#Termination with one or only 2 edges at vertex
	elsif (vd.leds.length == 2)
		vecA = vpos.vector_to ptA
		vecB = vpos.vector_to ptB
		section = profile_compute_by_vectors profile, ptA, vecA, ptB, ed.vec * vecB
	
	#Termination with 3 edges or more
	else
		edA = pairA.leds.find {|edd| edd != ed }
		edB = pairB.leds.find {|edd| edd != ed }

		vecA = edA.vec
		vecA = vecA.reverse if vecA % vpos.vector_to(edA.ptmid) < 0
		vecB = edB.vec
		vecB = vecB.reverse if vecB % vpos.vector_to(edB.ptmid) < 0
		
		section = profile_compute_by_vectors(profile, ptA, vecA, ptB, ed.vec * vecB)
	end	
	
	section = section.reverse unless section[0].on_plane?(ed.facemain.plane)
	ed.cross_sections[icorner] = section
	
	return section
end

#Construct the lattes for the rounding of an edge
def build_lattes(i, xsection1, xsection2, sh_section1, sh_section2)
	lpts = [xsection1[i], xsection2[i]]
	
	if sh_section2 && sh_section2[i] && sh_section2[i].length > 0
		sh_section2[i].each do |pt|
			lpts.push pt unless pt == xsection2[i] || pt == xsection2[i+1]
		end
	end

	lpts.push xsection2[i+1], xsection1[i+1]

	if sh_section1 && sh_section1[i] && sh_section1[i].length > 0
		lpt1 = []
		sh_section1[i].each do |pt|
			lpt1.push pt unless pt == xsection1[i] || pt == xsection1[i+1]
		end
		lpts += lpt1.reverse
	end
	
	lpts
end

#Compute the round border on an edge as a vmesh
def compute_mesh_edge(ed)
	xsection1 = ed.cross_sections[0]
	xsection2 = ed.cross_sections[1]
	sh_section1 = ed.sharp_sections[0]
	sh_section2 = ed.sharp_sections[1]
	
	nblat = xsection1.length - 1
	n2 = nblat / 2
	n1 = nblat - n2
	
	#Part close to main face
	face1 = ed.facemain
	mesh1 = []
	for i in 0..n1-1
		mesh1.push build_lattes(i, xsection1, xsection2, sh_section1, sh_section2)
	end	
	normalref1 = compute_normal_reference(ed, face1, xsection1[0], xsection1[1]) if n1 > 0
	
	#Part close to the other face
	face2 = (ed.lfaces[0] == ed.facemain) ? ed.lfaces[1] : ed.lfaces[0]
	mesh2 = []
	for i in n1..nblat-1
		mesh2.push build_lattes(i, xsection1, xsection2, sh_section1, sh_section2)
	end	
	normalref2 = compute_normal_reference(ed, face2, xsection1[n1+1], xsection1[n1]) if n2 > 0
	
	#Creating the vmesh, single or double
	ed.vmesh = []
	if (ed.edge.reversed_in?(face1) == ed.edge.reversed_in?(face2)) ||
	   (face1.material != face2.material) || (face1.back_material != face2.back_material)
		ed.vmesh.push [mesh1, face1, normalref1] if n1 > 0
		ed.vmesh.push [mesh2, face2, normalref2] if n2 > 0
	else
		ed.vmesh.push [mesh1 + mesh2, face1, normalref1]
	end	
	
	#Creating the border edges
	ed.vborders = []
	ed.vborders.push [[xsection1.first, xsection2.first], 'S']
	ed.vborders.push [[xsection1.last, xsection2.last], 'S']

	#Termination edges
	xs = [xsection1, xsection2]
	for i in 0..1
		ed.vborders.push [xs[i], 'P'] if ed.corners[i].leds.length == 1
	end	
end

#Compute the round border on an edge as a vmesh
def compute_mesh_edge_triangulated(vd, ed1, ed2, seg1, seg2)
	icorner1 = (ed1.corners[0] == vd) ? 0 : 1
	icorner2 = (ed2.corners[0] == vd) ? 0 : 1
	xsection1 = ed1.cross_sections[icorner1]
	xsection2 = ed2.cross_sections[icorner2]
	if (xsection1.first.distance(xsection2.first) > xsection1.first.distance(xsection2.last)) &&
	   (xsection1.last.distance(xsection2.last) >= xsection1.last.distance(xsection2.first))
		xsection2 = xsection2.reverse
		reversed = true
	else	
		reversed = false
	end	
	
	nblat = xsection1.length - 1
	n2 = nblat / 2
	n1 = nblat - n2
	
	#Part close to main face
	face1 = ed1.facemain
	mesh1 = []
	for i in 0..n1-1
		mesh1.push build_lattes(i, xsection1, xsection2, nil, nil)
	end	
	normalref1 = compute_normal_reference(ed1, face1, xsection1[0], xsection1[1]) if n1 > 0
	
	#Part close to the other face
	face2 = (reversed) ? ed2.facemain : ed2.lfaces.find { |f| f != ed2.facemain }
	mesh2 = []
	for i in n1..nblat-1
		mesh2.push build_lattes(i, xsection1, xsection2, nil, nil)
	end	
	normalref2 = compute_normal_reference(ed2, face2, xsection1[n1+1], xsection1[n1]) if n2 > 0
	
	#Creating the vmesh, single or double
	if true || (face1.material != face2.material) || (face1.back_material != face2.back_material)
		@lst_vmesh_triangulated.push [mesh1, face1, normalref1] if n1 > 0
		@lst_vmesh_triangulated.push [mesh2, face2, normalref2] if n2 > 0
	else
		@lst_vmesh_triangulated.push [mesh1 + mesh2, face1, normalref1]
	end	
	
	#Creating the border edges
	@lst_vborders_triangulated.push [[xsection1.first, xsection2.first], 'S'] if xsection1.first != xsection2.first
	@lst_vborders_triangulated.push [[xsection1.last, xsection2.last], 'S'] if xsection1.last != xsection2.last
	
end


#---------------------------------------------------------------------------------------------------------------------------
# Methods to compute face orientation
#---------------------------------------------------------------------------------------------------------------------------

#Sort corners for face orientation
def sort_corners_for_orientation(vda, vdb)
	eda = vda.gold_ed
	edb = vdb.gold_ed
	return -1 if vda.convex
	return 1 if vdb.convex
	if eda && edb
		return eda.golden <=> edb.golden
	elsif eda
		return -1
	elsif edb
		return 1
	else
		return 0
	end	
end

#Compute right orientation at corner
def orientation_at_corner(vd)
	edgold = vd.gold_ed
	return unless edgold
	ledface = []
	vd.leds.each do |ed|
		ed.lfaces.each do |face|
			next if ed == edgold
			if ed_common_face?(ed, face, edgold)
				iface = (ed.lfaces[0] == face) ? 0 : 1
				ledface.push [ed, iface]
				break
			end
		end
	end	
	main = (ledface.find { |ll| ll[0].facemain }) ? 0 : 1
	ledface.each do |ll|
		ed = ll[0]
		iface = ll[1]
		iface2 = (iface + main).modulo(2)
		ed.facemain = ed.lfaces[iface2] unless ed.facemain
		catena = ed.catenas[iface2]
		assign_ed_facemain catena unless catena.nbsmall > 0
		ed.catenas[iface].nbsmall += 1
	end		
end

#Find if 2 edges share a common face
def ed_common_face?(ed0, face0, ed1)
	ed1.lfaces.each do |face|
		return true if face.normal.parallel?(face0.normal)
	end
	false
end

#Compute all face orientation - Needed for odd number of segments
def compute_face_orientation

	#Starting with the round corners
	lcorners = @hsh_corners.values.sort { |vda, vdb| sort_corners_for_orientation vda, vdb }
	
	hsh = {}
	lcorners.each do |vd|
		next if hsh[vd.object_id]
		hsh[vd.object_id] = true
		edgold = vd.gold_ed
		next unless edgold
		orientation_at_corner vd
		vd2 = (edgold.corners[0] == vd) ? edgold.corners[1] : edgold.corners[0]
		if vd2 && !hsh[vd2.object_id]
			hsh[vd2.object_id] = true
			orientation_at_corner vd2
		end
	end	
	
	#Assigning the main faces to edge
	@lst_catenas.each { |catena| assign_ed_facemain catena if catena.nbsmall == 0 }
	
	#Remaining edges
	@hsh_ed.each { |key, ed| ed.facemain = ed.lfaces[0] unless ed.facemain }
end

#Propagate fcae orientation on a catena
def assign_ed_facemain(catena)
	catena.chain.each do |ll|
		ed = ll[0]
		iface = ll[1]
		other_catena = (ed.catenas[0] == catena) ? ed.catenas[1] : ed.catenas[0]
		ed.facemain = ed.lfaces[iface] unless ed.facemain
		other_catena.nbsmall += 1
	end	
end

#---------------------------------------------------------------------------------------------------------------------------
# Detremination of golden edges at corners
#---------------------------------------------------------------------------------------------------------------------------

#Scan all corners for determining golden edges
def corner_scan_all
	@hsh_corners.each do |key, vd|
		corner_scan_golden vd
	end
	@hsh_corners.each do |key, vd|
		corner_determine_golden vd
	end

	@hsh_corners.each do |key, vd|
		corner_round_profile vd
	end
	@hsh_corners.each do |key, vd|
		corner_round_profile vd
	end
end

#Search and propagate the Golden edges
def corner_scan_golden(vd)
	
	if vd.leds.length == 4
		vd.leds = vd.leds.sort { |eda, edb| compare_gold_ed(vd, eda, edb) }
		vd.gold_ed = vd.leds[0]
	end
	
	return if vd.leds.length != 3
	
	#Identifying convex corners
	leds = vd.leds
	pairs = vd.pairs
	pairs.each do |pair|
		ed1 = pair.leds[0]
		ed2 = pair.leds[1]
		ed0 = leds.find { |ed| ed != ed1 && ed != ed2 }
		if pair.convex
			ed0.golden = -1
			vd.gold_ed = ed0
		end	
		otherpairs = pairs.find_all { |p| p != pair }
		if otherpairs[0].leds.include?(ed1)
			pair1 = otherpairs[0]
			pair2 = otherpairs[1]
		else
			pair1 = otherpairs[1]
			pair2 = otherpairs[0]
		end
		ed0.aligned = true if Geom.intersect_line_line([pair1.ptcross, ed1.vec], [pair2.ptcross, ed2.vec])
	end
end

#Compare two edges to detremine which one has to be gold
def compare_gold_ed(vd, eda, edb)
	return eda.golden <=> edb.golden if vd.convex 
	if eda.aligned && !edb.aligned
		return -1
	elsif edb.aligned && !eda.aligned
		return +1
	end	
	cosa = eda.cos
	cosb = edb.cos
	val = ((eda.cos - edb.cos).abs < 0.5) ? 0 : (eda.cos <=> edb.cos)
	val = -((eda.vec % @golden_axis).abs <=> (edb.vec % @golden_axis).abs) if val == 0
	val = -(eda.edge.length <=> edb.edge.length) if val == 0
	val
end

#Detremine the golden edge at a 3 edges corner
def corner_determine_golden(vd)
	return if vd.leds.length != 3
	lsgold = vd.leds.sort { |eda, edb| compare_gold_ed(vd, eda, edb) }
	ed0 = lsgold[0]
	vd.gold_ed = ed0 unless vd.gold_ed
	vd.pairs.each do |pair|
		next if pair.leds.include?(ed0)
		vd.round_pair = pair
	end	
end

#Calculate whether edges at a vertex should have a round profile
def corner_round_profile(vd)
	return if vd.leds.length != 3
	ed0 = vd.gold_ed
	ledothers = vd.leds.find_all { |ed| ed != ed0 }
	ed1 = ledothers[0]
	ed2 = ledothers[1]
	
	ed0.round_profile = true if use_round_profile?(ed0)
	if (use_round_profile?(ed1) || use_round_profile?(ed2))
		ed1.round_profile = true
		ed2.round_profile = true
	end		
end

#Get the profiles for the edge depending on the Hybrid settings
def use_round_profile?(ed)
	return true if ed.round_profile
	case @mode_profile
	when -1
		status = false
	when 0
		status = (ed.golden == -1) ? true : false
	else
		status = (ed.golden == -1) || (ed.corners[0].gold_ed == ed || ed.corners[1].gold_ed == ed)
	end	
	status
end

#---------------------------------------------------------------------------------------------------------------------------
# Calculate Rounding at corner, if applicable
#---------------------------------------------------------------------------------------------------------------------------

def rounding_compute(vd)
	#testing if worth doing
	return unless @mode_rounding
	return if @num_seg == 1 || (@mode_sharp && !vd.convex) || vd.leds.length != 3
	
	#Getting the Golden edge and determining the metal pair
	ed_gold = vd.gold_ed	
	pair_gold = vd.pairs.find { |pair| ! pair.leds.include?(ed_gold) }
	pair_silver = nil
	pair_bronze = nil
	ed_silver = nil
	ed_bronze = nil
	facemain = ed_gold.facemain
	otherface = (ed_gold.lfaces[0] == facemain) ? ed_gold.lfaces[1] : ed_gold.lfaces[0]
	vd.pairs.each do |pair|
		next if pair == pair_gold
		if pair.ptcross.on_plane?(facemain.plane)
			pair_silver = pair
			ed_silver = (pair.leds[0] == ed_gold) ? pair.leds[1] : pair.leds[0]
		else
			pair_bronze = pair
			ed_bronze = (pair.leds[0] == ed_gold) ? pair.leds[1] : pair.leds[0]
		end	
	end	
	return unless pair_silver && pair_bronze
	
	#Calculate the planes for the golden edge
	gold_ptcross = pair_gold.ptcross
	plane_silver = [pair_silver.ptcross, ed_silver.vec]
	line_silver = [gold_ptcross, ed_silver.vec]
	plane_bronze = [pair_bronze.ptcross, ed_bronze.vec]
	line_bronze = [gold_ptcross, ed_bronze.vec]
	pt_silver = Geom.intersect_line_plane [gold_ptcross, ed_silver.vec], [pair_silver.ptcross, ed_silver.vec]
	pt_bronze = Geom.intersect_line_plane [gold_ptcross, ed_bronze.vec], [pair_bronze.ptcross, ed_bronze.vec]
	
	#Rounding is not materialized
	return if pt_silver == nil || pt_bronze == nil || pt_silver == pt_bronze || 
	          pt_silver == gold_ptcross || pt_bronze == gold_ptcross
	
	#Computing the rounding
	vpos = vd.vertex.position
	vec1 = vpos.vector_to ed_silver.ptmid
	vec2 = vpos.vector_to ed_bronze.ptmid
	normal2 = vec1 * vec2
	offset1 = gold_ptcross.distance pt_silver
	offset2 = gold_ptcross.distance pt_bronze
	pair_gold.rounding = profile_compute_by_offset ['C', @num_seg], gold_ptcross, vec1, offset1, vec2, offset2
end

#---------------------------------------------------------------------------------------------------------------------------
# Corner calculation
#---------------------------------------------------------------------------------------------------------------------------

#Top method to compute a corner
def corner_compute(vd)
	leds = vd.leds
	mode_sharp = @mode_sharp
	
	case vd.leds.length
	
	#Extremity corners or corner with 2 edges
	when 1, 2
		corner_compute_12 vd
		
	#Corner with 3 edges	
	when 3
		if @num_seg == 1
			if vd.convex
				corner_compute_3round vd
			else
				corner_compute_3round vd
			end	
		elsif (!mode_sharp && @num_seg != 1) || vd.convex
			corner_compute_3round vd
		elsif @strict_offset
			corner_compute_any_sharp vd
		else	
			corner_compute_3sharp vd
		end
		
	#Corner with 4 edges	
	when 4
		if mode_sharp
			corner_compute_any_sharp vd
		else	
			corner_compute_round_4 vd
		end	
		
	#Corner with more than 4 edges	
	else
		if mode_sharp
			corner_compute_any_sharp vd
		else	
			corner_compute_star vd
		end	
	end	
end

#Compute a corner with 1 or 2 edges (sharp)
def corner_compute_12(vd)
	leds = vd.leds
	leds.each do |ed|
		icorner = (ed.corners[0] == vd) ? 0 : 1
		section = compute_sections_multi ed, icorner
	end
end

#---------------------------------------------------------------------------------------------------------------------------
# Catena calculations: Chain of edges with related offsets
#---------------------------------------------------------------------------------------------------------------------------

#Compute the extension of a catena for an edge, and its face and corner
def catena_extend(catena, chain, ed, iface, icorner)
	pair = ed.pairs[2 * icorner + iface]
	return nil unless pair
	return nil if pair.alone
	ed2 = pair.leds.find { |edd| edd != ed }
	n = 0
	ed2.pairs.each_with_index do |pair2, i|
		if pair2 == pair
			n = i
			break
		end
	end
	iface2 = n.modulo(2)
	return nil if ed2.catenas[iface2] == catena
	icorner2 = (n - iface2) / 2
	icorner2 = (icorner2 + 1).modulo(2)
	chain.push [ed2, iface2]
	ed2.catenas[iface2] = catena
	[ed2, iface2, icorner2]
end

#Compute the half chain in a direction for an edge
def catena_compute_half_by_face(catena, ed, iface, icorner)
	chain = []
	ll = [ed, iface, icorner]
	while true
		ll = catena_extend catena, chain, ll[0], ll[1], ll[2]
		break if ll == nil || ll[0] == ed
	end	
	chain
end

#Compute the catena for a initial edge
def catena_compute(ed, iface)
	catena = catena_create
	ed.catenas[iface] = catena
	chain1 = catena_compute_half_by_face catena, ed, iface, 0
	chain2 = []
	if chain1.last == nil || chain1.last[0] != ed
		chain2 = catena_compute_half_by_face catena, ed, iface, 1
	end
	catena.chain = chain2.reverse + [[ed, iface]] + chain1
	catena
end

#Create a Catena structure
def catena_create
	catena = RoundCorner::RDC_Catena.new
	catena.chain = []
	catena.leds = []
	catena.nbsmall = 0
	@lst_catenas.push catena
	catena
end

#Compute all catenas in the selection
def catena_compute_all
	@hsh_ed.each do |key, ed|
		for iface in 0..1
			unless ed.catenas[iface]
				catena_compute ed, iface
			end	
		end	
	end
	
	#sorting the eds by angle
	@lst_catenas.each_with_index do |catena, i|
		catena.leds = catena.chain.collect { |ll| ll[0] }
		catena.leds.sort! { |ed1, ed2| ed1.cos <=> ed2.cos }
	end	
	
	#sorting the catenas"
	@lst_catenas.each_with_index do |catena, i|
		catena_sort catena
	end	
end

def catena_offset_all
	@lst_catenas.each do |catena|
		icur = catena_begin_ed(catena)
		while catena_propagate_offset_factors(catena, icur, +1) 
			icur += 1
		end	
		while catena_propagate_offset_factors(catena, icur, -1) 
			icur -= 1
		end	
	end	
end

def catena_begin_ed(catena)
	#Assigning offset 1 to the first edge in the sorted list
	ed = catena.leds[0]
	catena.chain.each_with_index do |ll, icur|
		if ll[0] == ed
			ed.lfactors[ll[1]] = 1.0 if @strict_offset
			return icur
		end	
	end
end

#Propagate the offset factors from an edge to the next one
def catena_propagate_offset_factors(catena, icur, incr)
	chain = catena.chain

	#Checking if end of chain
	inext = icur + incr
	return false unless inext >= 0
	llnext = catena.chain[inext]
	return false unless llnext
	
	#Current edge
	llcur = catena.chain[icur]
	edcur = llcur[0]
	ifacecur = llcur[1]
	edgecur = edcur.edge
	facecur = edcur.lfaces[ifacecur]
	ptmidcur = edcur.ptmid
	factorcur = edcur.lfactors[ifacecur]
	
	#Next edge
	ednext = llnext[0]
	ifacenext = llnext[1]
	edgenext = ednext.edge
	facenext = ednext.lfaces[ifacenext]
	ptmidnext = ednext.ptmid
		
	#Computing the intersection of the two face planes
	normalcur = facecur.normal
	normalnext = facenext.normal
	if normalcur.parallel?(normalnext)
		ednext.lfactors[ifacenext] = edcur.lfactors[ifacecur] if @strict_offset
		return true
	end	
	lineinter = Geom.intersect_plane_plane [ptmidcur, normalcur], [ptmidnext, normalnext]
	
	#Computing the intersection of the offset lines with the intersection of faces
	ptcur = Geom.intersect_line_line edcur.parallels[ifacecur], lineinter
	ptnext = Geom.intersect_line_line ednext.parallels[ifacenext], lineinter
	return false unless ptcur && ptnext

	#Common vertex
	llv = edgecur.vertices & edgenext.vertices
	vertex = llv[0]
	vpos = vertex.position
	
	#Computing the factor for next edge
	dcur = vpos.distance ptcur
	dnext = vpos.distance ptnext	
	ednext.lfactors[ifacenext] = factorcur * dcur / dnext if @strict_offset
	
	true
end

def catena_sort(catena)
	catena.chain.each do |ll|
		ed = ll[0]
		iface = ll[1]
	end	
end

#---------------------------------------------------------------------------------------------------------------------------
# Creation of Geometry
#---------------------------------------------------------------------------------------------------------------------------

#Execute the rouding Edges
def execute
	t0 = Time.now.to_f
	return unless prepare_geometry
	
	@running = 1
	
	@mentities = @model.active_entities
	@lst_corners = @hsh_corners.values
	@nb_corners = @lst_corners.length - 1
	@lst_eds = @hsh_ed.values
	@nb_eds = @lst_eds.length - 1
	@hsh_edge_erase = {}
	@nb_borders = @lpt_borders.length / 2 - 1
	@grp_tempo = nil
	
	@deltayield = 1.0
	@run_timer = nil
	
	nbsteps = @nb_borders + @nb_eds + 3 * @nb_corners + 1 + 5
	@pbar = Traductor::ProgressionBar.new nbsteps, "Processing"
	@tbeg = Time.now.to_f
	@tyield = @tbeg
	process_geometry 0, 0 
end

#Abort the operation
def abort_geometry
	abort_operation
	@end_proc.call 0 if @end_proc
	restart
end

#Interface to the main tool to know if the geometry creation is being executed
def running?
	@running
end
	
#Determine if this is time to give back control to the GUI and does it if so	
def time_to_yield?(step, ipos)
	#Interruption requested
	if @ask_interrupt
		UI.stop_timer @run_timer if @run_timer
		@run_timer = nil
		@ask_interrupt = false
		aborting = false
		if @reversible_interrupt
			status = UI.messagebox @msg_abort, MB_YESNO 
			aborting = true if status == 6
		else
			status = UI.messagebox @msg_abort_leave, MB_YESNO 
			if status == 6
				@deltayield = 60
				return false
			else
				aborting = true
			end	
		end	
		if aborting
			abort_geometry
			return true
		else
			@tyield = Time.now.to_f
		end	
	end	
	
	#Checking if time to yield to UI
	if Time.now.to_f - @tyield > @deltayield
		@tyield = Time.now.to_f
		@run_timer = UI.start_timer(0.1, false) { process_geometry(step, ipos) }
		return true
	end
	@pbar.countage
	false	
end

def interrupt?(reversible=true)
	return false unless @running
	@ask_interrupt = true
	@reversible_interrupt = reversible
end

#Create the final geometry
def process_geometry(step, ipos)
	
	#Initialization and starting the transaction
	if step == 0
		start_operation unless @operation
	
		#Creating the borders
		for k in ipos..@nb_borders
			return if time_to_yield?(step, k)
			grp = @mentities.add_group
			edges = grp.entities.add_edges @lpt_borders[2 * k], @lpt_borders[2 * k + 1]
			apply_edge_prop edges, @prop_borders if edges
			grp.explode
		end	
		step += 1
		ipos = 0
	end
	
	#Creating extra edges for standalone terminations
	if step == 1
		for k in ipos..@nb_corners
			return if time_to_yield?(step, k)
			vd = @lst_corners[k]
			next unless vd.leds.length == 1
			ptv = vd.vertex.position
			vd.pairs.each do |pair|
				edges = @mentities.add_edges ptv, pair.ptcross
				####apply_edge_prop edges, @prop_borders
				edge = edges[0]
				@hsh_edge_erase[edge.entityID] = edge
			end	
		end
		@lst_edge_erase = @hsh_edge_erase.values
		@nb_edge_erase = @lst_edge_erase.length - 1
		step += 1
		ipos = 0
	end
	
	#Creating the rounding faces
	if step == 2
		@grp_tempo = @mentities.add_group unless @grp_tempo
		for k in ipos..@nb_eds
			return if time_to_yield?(step, k)
			ed = @lst_eds[k]
			create_geometry_vmesh ed.vmesh, @grp_tempo.entities if ed.vmesh
			create_geometry_vborders ed.vborders, @grp_tempo.entities, false if ed.vborders
		end
		step += 1
		ipos = 0		
	end	
	
	#Creating the triangulated vmesh if any
	if step == 3
		@grp_tempo = @mentities.add_group unless @grp_tempo
		create_geometry_vmesh @lst_vmesh_triangulated, @grp_tempo.entities
		create_geometry_vborders @lst_vborders_triangulated, @grp_tempo.entities
		step += 1
		ipos = 0		
	end
	
	#creating the corner round faces
	if step == 4
		@grp_tempo = @mentities.add_group unless @grp_tempo
		for k in ipos..@nb_corners
			if k == @nb_corners
				@pbar.set_label @label_wait
				@running = 2
				@cursor_proc.call if @cursor_proc
			end	
			return if time_to_yield?(step, k)
			vd = @lst_corners[k]
			create_geometry_vmesh vd.vmesh, @grp_tempo.entities if vd.vmesh
		end	
		@grp_tempo.explode
		for k in 0..@nb_borders
			edges = @mentities.add_edges @lpt_borders[2 * k], @lpt_borders[2 * k + 1]
			apply_edge_prop edges, @prop_borders
		end	
		step += 1
		ipos = 0
	end
	
	#Destroying the vertices
	if step == 5
		if ipos == 0
			@running = 3
			@cursor_proc.call if @cursor_proc
		end	
		@pbar.set_label @label_finishing
		for k in ipos..@nb_corners
			return if time_to_yield?(step, k)
			vd = @lst_corners[k]
			if vd.leds.length == 1
				edge = vd.leds[0].edge
				@mentities.erase_entities edge if edge.valid?
			else
				@mentities.erase_entities vd.vertex.edges if vd.vertex.valid?
			end	
		end
		step += 1
		ipos = 0
	end
	
	#Additional cleanup
	if step == 6
		@pbar.countage
		for k in ipos..@nb_edge_erase
			edge = @lst_edge_erase[k]
			next unless edge.valid?
			edge.find_faces
			if edge.faces.length <= 1
				@mentities.erase_entities edge
			end	
		end
	end
	
	commit_operation
	@run_timer = nil
	@end_proc.call(Time.now.to_f - @tbeg) if @end_proc
	
	#Clean up selection
	unselect_all
end
	
def apply_edge_prop(ledges, prop)
	return unless ledges
	soft = (prop =~ /S/i) ? true : false
	smooth = (prop =~ /M/i) ? true : false
	hidden = (prop =~ /H/i) ? true : false
	ledges.each do |e| 
		next unless e.valid?
		e.soft = soft ; e.smooth = smooth ; e.visible = !hidden
	end	
end
	
#Create geometry for a virtual mesh	
def create_geometry_vborders(vborders, entities, nostyle=false)
	vborders.each do |vb|
		lpt = vb[0]
		style = vb[1]
		edges = entities.add_edges lpt
		next if nostyle
		case style
		when /S/i
			apply_edge_prop edges, @prop_borders
		when /P/i
			edges.each { |e| e.soft = false ; e.smooth = false }
		end	
	end	
end
	
#Create geometry for a virtual mesh	
def create_geometry_vmesh(vmesh, entities)
	vmesh.each do |vm|
		faceref = vm[1]
		normalref = vm[2]
		mat = faceref.material
		bmat = faceref.back_material
		allfaces = []
		normal = normalref
		vm[0].each do |lpt|
			lfaces = make_face entities, lpt, nil, normal
			next if lfaces.length == 0
			normal = lfaces.last.normal
			allfaces += lfaces
			lfaces.each do |face|
				apply_edge_prop face.edges, @prop_rounds
			end
		end	
		allfaces.each { |face| face.reverse! } if normalref && allfaces[0] && allfaces[0].normal % normalref < 0
		allfaces.each { |face| face.material = mat } if mat
		allfaces.each { |face| face.back_material = bmat } if bmat
	end
end
	
#Prepare the geometry
def prepare_geometry
	@current_vd = nil
	begin
		#Compute the corners
		@hsh_corners.each { |key, vd| corner_compute(@current_vd = vd) }
		
		#Compute the edge roundings
		@hsh_ed.each { |key, ed| compute_mesh_edge ed }
		
		#Compute the edge roudings for triangulated corners if any
		@lst_vmesh_triangulated = []
		@lst_vborders_triangulated = []
		@lst_triangulated.each { |ll| compute_mesh_edge_triangulated ll[0], ll[1], ll[2], ll[3], ll[4] }	
	
	rescue
		signal_error_vd(@current_vd)
		@view.invalidate
		UI.messagebox T6[:MSG_ErrorFound]
		abort_geometry
		return false
	end
	true
end

#Signal errors in preparing the geometry
def signal_error_vd(vd)
	@hsh_error_vertex[vd.vertex.entityID] = vd.vertex if vd
end

#---------------------------------------------------------------------------------------------------------------------------
# Profiling methods
#---------------------------------------------------------------------------------------------------------------------------

#Compute the transformed profile by indicating the start and end point, and the start vector and end face
def profile_compute_by_vectors(profile_type, pt1, vec1, pt2, normal2)
	origin = Geom.intersect_line_plane [pt1, vec1], [pt2, normal2]
	offset1 = origin.distance pt1
	offset2 = origin.distance pt2
	vec2 = origin.vector_to pt2
	profile_compute_by_offset profile_type, origin, vec1, offset1, vec2, offset2
end

def profile_compute_by_vectors2(profile_type, pt1, vec1, pt2, vec2)
	lpt = Geom.closest_points [pt1, vec1], [pt2, vec2]
	origin = Geom.linear_combination 0.5, lpt[0], 0.5, lpt[1]
	offset1 = origin.distance pt1
	offset2 = origin.distance pt2
	vec1 = origin.vector_to pt1
	vec2 = origin.vector_to pt2
	profile_compute_by_offset profile_type, origin, vec1, offset1, vec2, offset2
end

#Compute the transformed profile by indicating the offsets and direction on faces
def profile_compute_by_offset(profile_type, origin, vec1, offset1, vec2, offset2)	
	#Getting the nominal profile
	pts = nominal_profile profile_type

	#Transformation from golden normalized form
	coef = [0, -offset1, 0, 0] + [-offset1, 0, 0, 0] + [0, 0, 1, 0] + [offset1, offset1, 0, 1]
	tsg = Geom::Transformation.new coef
	
	#Scaling and shearing to adjust differences of offset and angle
	angle = 0.5 * Math::PI - vec1.angle_between(vec2)
	tgt = Math.tan angle
	fac = offset2 / offset1 * Math.cos(angle)
	coef = [1, 0, 0, 0] + [0, fac, 0, 0] + [0, 0, 1, 0] + [0, 0, 0, 1]
	ts = Geom::Transformation.new coef
	coef = [1, 0, 0, 0] + [tgt, 1, 0, 0] + [0, 0, 1, 0] + [0, 0, 0, 1]
	tsh = Geom::Transformation.new coef
	
	#Transforming to match given coordinates at origin, vec1, vec2
	normal = vec1 * vec2
	taxe = Geom::Transformation.axes origin, vec1, normal * vec1, normal
	t = taxe * tsh * ts * tsg
	
	#Performing the transformation
	pts.collect { |pt| t * pt }
end

#Get the nominal profile and compute Nb of segment
def verify_profile(profile_type, numseg=nil)
	type = profile_type[0]
	@num_seg_lock = (type =~ /P/i) ? true : false
	if numseg && !@num_seg_lock
		@profile_type[1] = numseg
	end	
	pts = nominal_profile(@profile_type)
	@num_seg = pts.length - 1
end

#Get the nominal profile and compute Nb of segment
def nominal_profile(profile_type)
	#Computing the normalized profile in X, Y
	type = profile_type[0]
	param = profile_type[1]
	key = "#{type}-#{param}"
			
	#Standard profiles
	pts = @hsh_profile_pts[key]
	unless pts
		case type
		when 'BZ'
			pts = golden_bezier param
		when 'CR'
			pts = golden_circular_reverse param
		when /\AP/i
			pts = golden_perso param
		else
			pts = golden_circular param
		end
		@hsh_profile_pts[key] = pts
	end
	pts
end

def golden_circular(nb_seg)
	pts = []
	anglesec = 0.5 * Math::PI / nb_seg
	for i in 0..nb_seg
		angle = anglesec * i
		x = Math.cos(angle)
		y = Math.sin(angle)
		pts.push Geom::Point3d.new(x, y, 0)
	end	
	pts.reverse
end

def golden_circular_reverse(nb_seg)
	pts = []
	anglesec = 0.5 * Math::PI / nb_seg
	for i in 0..nb_seg
		angle = anglesec * i
		x = 1.0 - Math.sin(angle)
		y = 1.0 - Math.cos(angle)
		pts.push Geom::Point3d.new(x, y, 0)
	end	
	pts.reverse
end

def golden_bezier(nb_seg)
	pt1 = Geom::Point3d.new 0, 1, 0
	pt2 = Geom::Point3d.new 1, 1, 0
	pt3 = Geom::Point3d.new 1, 0, 0
	ctrl_pts = [pt1, pt2, pt3]
	BezierCurve.compute(ctrl_pts, nb_seg)
end

def golden_perso(fac)
	pt1 = Geom::Point3d.new 0, 1, 0
	pt2 = Geom::Point3d.new 1, 1, 0
	pt3 = Geom::Point3d.new 1, 0, 0
	pt4 = Geom::Point3d.new fac, fac, 0
	ctrl_pts = [pt1, pt2, pt3]
	crv1 = BezierCurve.compute ctrl_pts, 8
	ctrl_pts = [crv1[3], pt4, crv1[5]]
	crv2 = BezierCurve.compute ctrl_pts, 6
	crv = crv1[0..2] + crv2 + crv1[6..-1]
	crv
end

#---------------------------------------------------------------------------------------------------------------------------
# Some utilities
#---------------------------------------------------------------------------------------------------------------------------

#Build a face given a set of points
def make_face(entities, pts, faceref=nil, normal=nil)
	n = pts.length - 3
	return [] if n < 0
	lfaces = []
	begin
		face = entities.add_face(pts)
		lfaces.push face if face
	rescue
		begin
			face = entities.add_face(pts[0], pts[1], pts[2])
			lfaces.push face if face
			face = entities.add_face(pts[2], pts[3], pts[0])
			lfaces.push face if face
		rescue
		end	
	end	
	return [] if lfaces.length == 0
	if faceref || normal
		lfaces.each do |face|
			if normal
				face.reverse! if face.normal % normal < 0
			elsif faceref	
				face.reverse! if face.normal % faceref.normal < 0
			end	
			if faceref
				face.material = faceref.material
				face.back_material = faceref.back_material
			end	
		end
	end
	
	lfaces
end

#---------------------------------------------------------------------------------------------------------------------------
# Manage Sketchup Operations
#---------------------------------------------------------------------------------------------------------------------------

#Commit the operation
def commit_operation
	if @operation
		status = @model.commit_operation
		@operation = false
	end	
end

#Undo the operation	
def undo_operation
	commit_operation
	Sketchup.undo
end

#Abort the operation
def abort_operation
	if @operation
		@model.abort_operation
		@operation = false
	end	
end

#Start the operation
def start_operation(title=nil)
	G6.start_operation @model, ((title) ? title : "Round Corner"), true
	@operation = true
end

def continue_operation(title=nil)
	G6.continue_operation @model, ((title) ? title : "Round Corner"), true, false, true
	@operation = true
end
	
end	#class RoundCornerAlgo


#--------------------------------------------------------------------------------------------------------------
# Bezier methods
#--------------------------------------------------------------------------------------------------------------			 				   

class BezierCurve

# Evaluate the curve at a number of points and return the points in an array
def BezierCurve.compute(pts, numpts) 
    curvepts = []
    dt = 1.0 / numpts

    # evaluate the points on the curve
    for i in 0..numpts
        curvepts[i] = BezierCurve.evaluate(pts, i * dt)
    end 
    curvepts
end

def BezierCurve.evaluate(pts, t)
    degree = pts.length - 1
    return nil if degree < 1
   
    t1 = 1.0 - t
    fact = 1.0
    n_choose_i = 1

    x = pts[0].x * t1
    y = pts[0].y * t1
    z = pts[0].z * t1
    
    for i in 1...degree
		fact = fact * t
		n_choose_i = n_choose_i * (degree - i + 1) / i
        fn = fact * n_choose_i
		x = (x + fn * pts[i].x) * t1
		y = (y + fn * pts[i].y) * t1
		z = (z + fn * pts[i].z) * t1
    end

	x = x + fact * t * pts[degree].x
	y = y + fact * t * pts[degree].y
	z = z + fact * t * pts[degree].z

    Geom::Point3d.new(x, y, z)  
end 

end	#class BezierCurve
	
#--------------------------------------------------------------------------------------------------------------
# Tracing class
#--------------------------------------------------------------------------------------------------------------			 				   
	
class RoundTrace

@@mode_on = true
@@file = nil
@@time0 = 0
@@last_time = 0

def RoundTrace.create_file
	filename = File.join $:[0], "FredoTraceRoundCorner.txt"
	@@file = File.open(filename, "w")
	@@time0 = Time.now.to_f
	@@last_time = @@time0
	RoundTrace.trace "#{Time.now.asctime} - RUBY PLATFORM = #{RUBY_PLATFORM}"
end

def RoundTrace.close
	@@file.close
	@@file = nil
end

def RoundTrace.trace(text)
	return unless @@mode_on
	create_file unless @@file
	t0 = Time.now.to_f
	d0 = t0 - @@time0
	d = t0 - @@last_time
	@@file.puts "[#{sprintf("%3.6f", d0)} s] - [#{sprintf("%3.6f", d)} s]: " + text
	@@file.flush
	@@last_time = t0
end

end
	
end	#End Module RoundCorner
