=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed July 2013 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			:   JointPushPullGeometry.rb
# Original Date	:   24 Jul 2013
# Description	:   JointPushPull geometry generation
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_JointPushPull

T6[:VGBAR_TIT_TimeCalculation] = "Time calculation ="
T6[:VGBAR_TIT_GeneratingGeometry] = "Generating Geometry"
T6[:VGBAR_TIT_GeneratingGeometryNum] = "Generating Geometry - Block %1"

T6[:VGBAR_Step_prepare_mesh] = "Preparation of Mesh"
T6[:VGBAR_Step_generate_mesh] = "Generation of faces"
T6[:VGBAR_Step_transfer_edge_prop] = "Transfer Edge properties"
T6[:VGBAR_Step_transfer_material] = "Transfer Materials"
T6[:VGBAR_Step_erase_coplanar_edges] = "Erase Coplanar Edges"
T6[:VGBAR_Step_handle_finishing] = "Handle Finishing options"
T6[:VGBAR_Step_group_explode] = "Explode creation Group (cannot be interrupted and may take long)"
T6[:VGBAR_Step_transfer_curves] = "Transfer curves"

#=============================================================================================
#=============================================================================================
# GEOMETRY: Generation of the Geometry
#=============================================================================================
#=============================================================================================

class JointPushPullTool < Traductor::PaletteSuperTool

#---------------------------------------------------------------------------------------------
# GEOMETRY: Initialization and environment for Execution
#---------------------------------------------------------------------------------------------

#GEOMETRY: Execute the generation of Terrain geometry
def geometry_execute
	@last_offset = @offset

	#Initialiazing the state
	@geometry_processing = true
	@geometry_generated = false
	set_state_mode :geometry
	@suops.abort_operation
	
	#Handling non-Unique groups

	#Scanning the options useful for generation
	@option_thickening = option_get(:thickening)
	@option_borders = option_get(:borders)
	@option_gen_group = option_get(:gen_group)
	@option_color_border_adjacent = (@param_color_adjacent && @jpp_mode != :round)
	auto_soften_angle = @param_auto_soften_borders.degrees
	@auto_soften_ps = (auto_soften_angle > 0) ? Math::cos(auto_soften_angle) : nil
	rs = option_get :radial_scaling
	@option_radial_scaling = (rs && rs != 1.0)
		
	#Initializing the Visual bar
	geometry_init_visual_bar
	
	#Generating the geometry
	@suops.start_execution { geometry_robot @suops }	
end

#GEOMETRY: Notification of the Termination of the geometry
def geometry_terminate(time)
	@vgbar.stop
	@vgbar = nil
	@message_palette = "#{T6[:VGBAR_TIT_TimeCalculation]} #{sprintf("%0.2f", time)} s"
	@message_level = 'yellow'
	@facepicker.reset
	@time_processing = time
	@geometry_generated = true
	set_state_mode :selection
	@geometry_processing = false
end

#GEOMETRY: Commit the generation of geometry
def geometry_commit
	if @geometry_generated
		@suops.commit_operation
		@geometry_generated = false
		start_context_operation
	end	
end

#---------------------------------------------------------------------------------------------
# GEOMETRY: Management of the visual progress bar
#---------------------------------------------------------------------------------------------

#VGBAR: Initialize the Visual progress bar and steps robot
def geometry_init_visual_bar
	@robot_steps = [:prepare_mesh, :generate_mesh, :transfer_edge_prop, :transfer_material, 
	                :erase_coplanar_edges, :group_explode, :handle_finishing, :transfer_curves].clone
			
	#Filtering out useless steps		
	@robot_steps.delete :group_explode if @option_gen_group
	
	#Computing the reference tables for steps
	@hsh_step_text = {}
	@hsh_step_num = {}
	@nb_steps_block = 0
	@robot_steps.each do |symb|
		@hsh_step_text[symb] = T6[("VGBAR_Step_#{symb}").intern]
		@hsh_step_num[symb] = @nb_steps_block
		@nb_steps_block += 1
	end

	#Computing the number of block effectively processed
	@icur_block = 0
	@nblocks = @lst_blocks.length
	@nb_steps_total = @nb_steps_block * @nblocks

	#Creating the visual bar
	if @nb_total_faces > 200
		delay = mini_delay = 0
	else
		delay = mini_delay = 0.5
	end	
	hsh = { :delay => delay, :mini_delay => mini_delay, :style_color => :bluegreen }
	title = T6[:VGBAR_TIT_GeneratingGeometry]
	@vgbar = Traductor::VisualProgressBar.new title, hsh
	@vgbar.start
end

#VGBAR: Main progression of the visual bar
def geometry_vgbar_progression(symb)
	istep = @icur_block * @nb_steps_block + @hsh_step_num[symb]
	ratio = 1.0 * istep / @nb_steps_total 
	@vgbar.progression ratio, @hsh_step_text[symb]
end

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

#---------------------------------------------------------------------------------------------
# GEOMETRY: Toplevel methods for geometry generation
#---------------------------------------------------------------------------------------------

#GEOMETRY: Main State Automat function to build the geometry
def geometry_robot(suops)
	begin
		geometry_robot_exec(suops)
	rescue Exception => e
		Traductor::RubyErrorDialog.invoke e, @title, T6[:ERR_Geometry]
		abort_tool
	end
end

def geometry_robot_exec(suops)
	while(action, *param = suops.current_step) != nil	
		case action
		when :_init
			next_step = [@robot_steps.first, 0]
		when :finish	
			next_step = nil
		else
			return if suops.yield?
			iblock, = param
			next_step = robot_call_action(action, iblock)
		end	
		break if suops.next_step(*next_step)
	end
end

#ROBOT: Invoke the next action and move in the chain of processing
def robot_call_action(action, iblock)
	#Final step for the block
	return [@robot_steps.first, iblock + 1] unless action
	
	#Skip if Block is part of a Component already handled
	block = @lst_blocks[iblock]
	return [:finish, 0] unless block

	#Change of block under treatment
	if action == :prepare_mesh
		@icur_block = iblock 
		@vgbar.update_title T6[:VGBAR_TIT_GeneratingGeometryNum, @icur_block+1] if @nblocks > 1
	end
	
	#Update the visual progress bar nd hourglass cursor
	geometry_vgbar_progression action
	@geometry_processing = (action == :group_explode) ? :red : :green
	onSetCursor

	#Switch on action
	case action
	when :prepare_mesh
		geometry_robot_prepare_mesh block
	when :generate_mesh
		geometry_robot_generate_mesh block
	when :transfer_edge_prop	
		geometry_robot_transfer_edge_prop block
	when :transfer_material	
		geometry_robot_transfer_material block
	when :erase_coplanar_edges	
		geometry_robot_erase_coplanar_edges block
	when :handle_finishing	
		geometry_robot_handle_finishing block
	when :group_explode	
		geometry_robot_group_explode block
	when :transfer_curves	
		geometry_robot_transfer_curves block
	end

	#Computing the next action
	istep_next = @robot_steps.rindex(action) + 1
	next_action = @robot_steps[istep_next]
	(next_action) ? [next_action, iblock] : [@robot_steps.first, iblock + 1]
end

#ROBOT: Prepare a mesh of polygon based on faces, borders and junctions
def geometry_robot_prepare_mesh(block)
	@top_entities = G6.grouponent_entities block.parent
	
	#Parameters for the block
	lst_pfaces = block.lst_pfaces
	hpvx = block.hsh_pvx
	trblock_inv = block.tr_inv
	thicken_reversal = block.reversal = geometry_block_reversal(block)
	
	#Optimizing the flattening of faces with holes
	geometry_optimize_flattening(block)
	
	#Registering the curves
	geometry_register_curves block
	
	#Creating the Polygon Mesh and the mapping original / new
	@mesh = Geom::PolygonMesh.new
	@geninfo = []
	
	#Creating the mesh for the top face
	lst_pfaces.each_with_index do |pface, i|
		geometry_create_top_face @geninfo, @mesh, pface, hpvx, trblock_inv, block.force_thicken
		geometry_create_bottom_face @geninfo, @mesh, pface, hpvx, trblock_inv, block.force_thicken
	end
	
	#Generating the mesh for the borders
	if @option_borders != :none
		geometry_create_borders @geninfo, @mesh, hpvx, block.hsh_ped, trblock_inv, thicken_reversal, block.force_thicken
	end
	
	#Generating the mesh for the junctions if any
	if @jpp_mode == :round
		geometry_create_roundings(block, @geninfo, @mesh, trblock_inv)
	end
end
	
#ROBOT: Generate the mesh
def geometry_robot_generate_mesh(block)
	#Generating the SU faces for the block from the Polygon mesh
	@group = @top_entities.add_group
	@group_entities = @group.entities
	@group_entities.fill_from_mesh @mesh, true, 12
	
	#Reconciling the information for generated faces
	hpvx = block.hsh_pvx
	tr = block.tr
	lfaces = @group_entities.grep(Sketchup::Face)
	lfaces.each_with_index do |face, i|
		pface, info = @geninfo[i]
		#check_geninfo(pface, info, face, hpvx, tr)
		info.unshift face
		pface.gen_info.push info
	end	
end
	
def check_geninfo(pface, info, face, hpvx, tr)
	code, itri = info
	
	if code == :t
		lfpt = face.vertices.collect { |v| tr * v.position }
		triangle = pface.ls_triangles[itri]
		triangle.each do |iv|
			pt = hpvx[iv].target
			unless lfpt.include?(pt)
				puts "TRiangle pas bon = #{pt}"
			end	
		end	
	end	
	
end
		
#ROBOT: Transfer edge properties to new geometry
def geometry_robot_transfer_edge_prop(block)
	hped = block.hsh_ped
	hpvx = block.hsh_pvx
	trblock_inv = block.tr_inv
	force_thicken = block.force_thicken
	ipos = (@offset > 0) ? 0 : 1

	#Transferring edge properties for edges 
	hsh_roundings = (@jpp_mode == :round) ? block.hsh_roundings[ipos] : nil
	hped.each { |ped_id, ped| geometry_transfer_edge_prop_top(ped, hpvx, hsh_roundings, trblock_inv, force_thicken) }
	
	#Adjusting edge properties for borders 
	if @option_borders != :none
		hsh_pvx_used = {}
		hped.each { |ped_id, ped| geometry_transfer_edge_prop_border(ped, hpvx, trblock_inv, hsh_pvx_used) }
	end	
	
	if @jpp_mode == :round
		block.hsh_junctions_vx[ipos].each do |jid ,junctions|
			nbj = junctions.length
			junctions.each do |junction|
				geometry_transfer_edge_prop_junction(junction, hpvx, trblock_inv, nbj) if junction
			end	
		end	
	end
end
	
#ROBOT: Transfer materials to new faces
def geometry_robot_transfer_material(block)
	hpvx = block.hsh_pvx
	trblock_inv = block.tr_inv
	thicken_reversal = block.reversal
	lst_pfaces = block.lst_pfaces

	#Transferring materials to faces
	lst_pfaces.each { |pface| geometry_transfer_material_top(pface, hpvx, trblock_inv, thicken_reversal) }

	#Transferring materials to borders
	lst_pfaces.each { |pface| geometry_transfer_material_borders(pface, hpvx, trblock_inv, thicken_reversal, block.force_thicken) }

	#Transferring materials to roundings
	lst_pfaces.each { |pface| geometry_transfer_material_roundings(pface, hpvx, trblock_inv, thicken_reversal) }
end

#ROBOT: Erase Coplanar Edge
def geometry_robot_erase_coplanar_edges(block)
	hsh_edges_erase = {}
	hpvx = block.hsh_pvx
	hped = block.hsh_ped
	trblock_inv = block.tr_inv
	block.lst_pfaces.each do |pface| 
		if pface.face.vertices.length > 5
			geometry_coplanar_edges_overhang(pface, hsh_edges_erase, hpvx, hped, trblock_inv)
		else	
			geometry_coplanar_edges(pface, hsh_edges_erase)
		end	
	end	
	ledges = hsh_edges_erase.values.find_all { |e| e }
	@group_entities.erase_entities ledges unless ledges.empty?
end

#ROBOT: Handle finishing
def geometry_robot_handle_finishing(block)
	return if @option_gen_group
	geometry_handle_finishing(block)
end

#ROBOT: Handle finishing
def geometry_robot_transfer_curves(block)
	geometry_transfer_original_curves(block) if @option_gen_group
	geometry_transfer_top_curves block
end

#ROBOT: Explode creation group
def geometry_robot_group_explode(block)
	return if @option_gen_group
	@group.explode
end

#---------------------------------------------------------------------------------------------
# PREPARE_MESH: Preparing the Mesh
#---------------------------------------------------------------------------------------------

#PREPARE_MESH: Optimize faces with holes and large number of edges
def geometry_optimize_flattening(block)
	return if @jpp_prop_flat
	return if @jpp_mode != :joint
	
	#Identifying faces with holes and large number of vertices
	lst_opt_faces = []
	block.lst_pfaces.each do |pface|
		face = pface.face
		if face.loops.length > 1
			lst_opt_faces.push [2000, face]
		else
			n = face.edges.length
			lst_opt_faces.push [n, face] if n > 5
		end
	end
	lst_opt_faces = lst_opt_faces.sort { |a, b| a.first <=> b.first }
	
	#Flattening the top faces
	hpvx = block.hsh_pvx
	lst_opt_faces.each do |i, face|
		lpt = []
		lpvx = []
		face_id = face.entityID
		face.vertices.each do |vx|
			vx_id = @proc_key_pseudo.call(vx.entityID, face_id)
			pvx = hpvx[vx_id]
			lpt.push pvx.target
			lpvx.push pvx
		end	
		plane = Geom.fit_plane_to_points(lpt)
		lpt = lpt.collect { |pt| pt.project_to_plane(plane) }
		lpvx.each_with_index { |pvx, i| pvx.target = lpt[i] }
	end
end

#PREPARE_MESH: Create the top face (and base face if generation as a Group)
def geometry_create_top_face(geninfo, mesh, pface, hpvx, trblock_inv, force_thicken)
	pface.gen_info = []
	face = pface.face
	face_id = face.entityID
	
	#TOP face with FLAT generated faces
	if @jpp_prop_flat || face.vertices.length == 3
	
		#faces with no holes
		if face.loops.length == 1
			pts = face.outer_loop.vertices.collect do |vx|
				id = @proc_key_pseudo.call(vx.entityID, face_id) 
				trblock_inv * hpvx[id].target
			end	
			return if @option_radial_scaling && geometry_pts_is_sharp?(pts)
			mesh.add_polygon pts
			geninfo.push [pface, [:t, 0, nil]]	

		#Faces with holes
		else
			pface.ls_triangles.each_with_index do |triangle, itri|
				pts = triangle.collect { |id| trblock_inv * hpvx[id].target }
				return if @option_radial_scaling && geometry_pts_is_sharp?(pts)
				mesh.add_polygon pts
				geninfo.push [pface, [:t, itri, nil]]
			end	
		end	
	
	#TOP face with non-FLAT generated faces - Use triangulation a priori
	else
		pface.ls_triangles.each_with_index do |triangle, itri|
			pts = triangle.collect { |id| trblock_inv * hpvx[id].target }
			return if @option_radial_scaling && geometry_pts_is_sharp?(pts)
			mesh.add_polygon pts
			geninfo.push [pface, [:t, itri, nil]]	
		end	
	end
	
end

#PREPARE_MESH: Check if a face if reduced to a single point
def geometry_pts_is_sharp?(pts)
	pt0 = pts.first
	!pts.find { |pt| pt != pt0 }
end
	
#PREPARE_MESH: Create the bottom face if generation as a Group in thickening mode
def geometry_create_bottom_face(geninfo, mesh, pface, hpvx, trblock_inv, force_thicken)
	return unless @option_gen_group && (@option_thickening || force_thicken)
	face = pface.face
	
	#Base face has NO holes - generate from its main loop
	if face.loops.length == 1
		pts = face.outer_loop.vertices.collect { |v| v.position }
		mesh.add_polygon pts
		geninfo.push [pface, [:o, 0, nil]]	

	#Base face has holes - generate from its triangulation
	else
		pface.ls_triangles.each_with_index do |triangle, itri|
			pts = triangle.collect { |id| trblock_inv * hpvx[id].origin }
			mesh.add_polygon pts
			geninfo.push [pface, [:o, itri, nil]]	
		end	
	end	
end

#PREPARE_MESH: Compute the border faces and insert them in the mesh
def geometry_create_borders(geninfo, mesh, hpvx, hsh_ped, trblock_inv, thicken_reversal, force_thicken)
	noborder = !@option_group && !@option_thickening && !force_thicken && @offset < 0
	hsh_ped.each do |ped_id, ped|
		next if @option_borders != :grid && !ped.at_border
		pvx0 = hpvx[ped.pvx1_id]
		pvx1 = hpvx[ped.pvx2_id]
		origin0 = pvx0.origin
		target0 = pvx0.target
		origin1 = pvx1.origin
		target1 = pvx1.target
		next if origin0 == target0 && origin1 == target1
		pts = [origin1, target1, target0, origin0].collect { |pt| trblock_inv * pt }
		pts = pts.reverse if thicken_reversal == :top
		
		geometry_quad_mesh pts, mesh, geninfo, ped.pface, :b, ped.matinfo
	end
end

#PREPARE_MESH: Compute the reversal of base or top faces for thickening
def geometry_block_reversal(block)
	return nil unless @option_thickening || block.force_thicken
	face0 = block.lst_pfaces[0].face
	pvx = block.hsh_pvx[@proc_key_pseudo.call(face0.vertices[0].entityID, face0.entityID)]
	vec = pvx.origin.vector_to(pvx.target)
	normal = G6.transform_vector(face0.normal, block.tr)
	(vec % normal > 0) ? :base : :top 
end

#-----------------------------------------------------------
# FINISHING: Manage the finishing (erase faces and edges)
#-----------------------------------------------------------

#FINISHING: Manage the finishing by erasing unnecessary edges
def geometry_handle_finishing(block)	
	hpvx = block.hsh_pvx
	hprotected = block.hsh_edge_protected
	thicken_reversal = block.reversal
	hpfaces = block.hpfaces

	#Thicken mode: just reverse the base
	if thicken_reversal == :base
		block.lst_pfaces.each { |pface| G6.reverse_face_with_uv(pface.face) if pface.face.valid? }
		return
	end	
	return if @option_thickening || block.force_thicken #|| @option_borders == :none
	
	#Deleting the original faces
	ls_erase = []
	hedges = {}
	block.lst_pfaces.each do |pface| 
		face = pface.face
		next unless face.valid?
		ls_erase.push face 
		face.edges.each { |e| hedges[e.entityID] = e unless geometry_edge_protected?(e, hprotected) }
	end	
	@top_entities.erase_entities ls_erase
	
	#Deleting their edges is alone or coplanar
	ls_erase = []
	hedges.each do |eid, e|
		ls_erase.push e if e.valid? && (e.faces.length <= 1 || G6.edge_coplanar?(e))
	end	
	
	#Noting the other edges at vertex
	halone = {}
	ls_erase.each do |e|
		e.start.edges.each { |ee| halone[ee.entityID] = ee }
		e.end.edges.each { |ee| halone[ee.entityID] = ee }
	end	
	
	#Erasing the relevant edges bordering the original face
	@top_entities.erase_entities ls_erase
	
	#Erasing the lonely edges if any left
	ls_erase = []
	halone.each { |eid, e| ls_erase.push e if e.valid? && e.faces.length == 0 }
	@top_entities.erase_entities ls_erase
end
	
#FINISHING	
def geometry_edge_protected?(edge, hprotected)
	pt1 = edge.start.position
	pt2 = edge.end.position
	hprotected.each do |eid, lpt|
		pe1, pe2 = lpt
		return true if (pt1 == pe1 && pt2 == pe2) || (pt1 == pe2 && pt2 == pe1)
	end
	false
end
	
#-----------------------------------------------------------
# CURVE: Manage the replication of curves
#-----------------------------------------------------------

#CURVE: Record curves to be transfered
def geometry_register_curves(block)
	geometry_identify_curves(block)
	return if block.hcurves.length == 0
	
	geometry_register_original_curves(block) if @option_gen_group
	
	if @jpp_prop_jointive && !@jpp_prop_random
		geometry_register_jointive_curves block
	else
		geometry_register_non_jointive_curves block
	end
end

#CURVE: Record curves to be transferred
def geometry_identify_curves(block)
	#Finding the curves based on edges
	hcurves = block.hcurves = {}
	block.hsh_ped.each do |ped_id, ped|
		curve = ped.edge.curve
		if curve
			hcurves[curve.entityID] = curve
			ped.curve = curve
		end	
	end
end

#CURVE: Record the original curves to be transferred (gen group only)
def geometry_register_original_curves(block)
	#List of vertices
	hpvx = block.hsh_pvx
	hsh_vx = {}
	hpvx.each do |pvx_id, pvx| 
		vx = pvx.vx
		hsh_vx[vx.entityID] = vx
	end
	
	#Registering the original curves if to be recreated in a group
	all_curves_ori = block.all_curves_ori = []
	block.hcurves.each do |curve_id, curve|
		vertices = curve.vertices
		llpt = []
		lpt = []
		vertices.each do |vx|
			if hsh_vx[vx.entityID]
				lpt.push vx.position
			else
				llpt.push lpt if lpt.length > 1
				lpt = []
			end	
			llpt.push lpt if lpt.length > 1
		end
		geometry_store_curves_points(all_curves_ori, llpt)
	end
end	

#CURVE: Store the curve points into the relevant array
def geometry_store_curves_points(all_curves, llpt)
	return if llpt.empty?
	if llpt.length > 1 && llpt.first.first == llpt.last.last
		lpt_last = llpt.last[0..-2]
		llpt[0] = lpt_last + llpt.first
		llpt = llpt[0..-2]
	end
	llpt.each { |lpt| all_curves.push lpt if lpt.length > 2 }
end
	
#CURVE: Record the target curves to be transferred (jointive mode)
def geometry_register_jointive_curves(block)
	trblock_inv = block.tr_inv
	hpvx = block.hsh_pvx
	all_curves_top = block.all_curves_top = []
	block.hcurves.each do |curve_id, curve|
		vertices = curve.vertices
		llpt = []
		lpt = []
		vertices.each do |vx|
			vx_id = vx.entityID
			pvx = hpvx[vx_id]
			if pvx
				lpt.push trblock_inv * pvx.target
			else
				llpt.push lpt if lpt.length > 1
				lpt = []
			end	
			llpt.push lpt if lpt.length > 1
		end
		geometry_store_curves_points(all_curves_top, llpt)
	end
end

#CURVE: Record the target curves to be transferred (non-jointive mode)
def geometry_register_non_jointive_curves(block)
	#Building a reference table between edges and ped for those which have curves
	hsh_edge_ped = {}
	block.hsh_ped.each do |ped_id, ped|
		next unless ped.curve
		edge_id = ped.edge.entityID
		lsped = hsh_edge_ped[edge_id] 
		lsped = hsh_edge_ped[edge_id] = [] unless lsped
		lsped.push ped
	end
	return unless hsh_edge_ped.length > 0
	
	hpvx = block.hsh_pvx
	all_curves_top = block.all_curves_top = []
	block.hcurves.each do |curve_id, curve|
		edges = curve.edges
		vertices = curve.vertices
		ls_sections = []
		section1 = []
		section2 = []
		llpt = []
		lpt = []
		
		#building the curve sections
		edges.each_with_index do |edge, i|
			edge_id = edge.entityID
			ped1, ped2 = hsh_edge_ped[edge_id]
			next unless ped1
			if geometry_curve_follow(section1, ped1, hpvx) || geometry_curve_follow(section1, ped2, hpvx) ||
			   geometry_curve_follow(section2, ped1, hpvx) || geometry_curve_follow(section2, ped2, hpvx)
				next
			end
			
			#Storing existing sections, if any, and starting new sections
			ls_sections.push section1 if section1.length > 2
			ls_sections.push section2 if section2.length > 2
			section1 = []
			section2 = []
			
			#Initializing new sections with the first 2 points
			vx1 = vertices[i]
			vx2 = vertices[i+1]
			pvx1 = hpvx[ped1.pvx1_id]
			pvx2 = hpvx[ped1.pvx2_id]
			if pvx1.vx == vx1
				section1.push pvx1.target, pvx2.target
			else	
				section1.push pvx2.target, pvx1.target
			end	
			if ped2
				pvx1 = hpvx[ped2.pvx1_id]
				pvx2 = hpvx[ped2.pvx2_id]
				if pvx1.vx == vx1
					section2.push pvx1.target, pvx2.target
				else	
					section2.push pvx2.target, pvx1.target
				end	
			end
		end
		ls_sections.push section1 if section1.length > 2
		ls_sections.push section2 if section2.length > 2
		
		#Applying the right transformation for the points of the curves
		trblock_inv = block.tr_inv
		llpt = []
		ls_sections.each do |lpt|
			llpt.push lpt.collect { |pt| trblock_inv * pt }
		end	
		geometry_store_curves_points(all_curves_top, llpt)
	end
end

#CURVE: check if a ped follow a section of curve
def geometry_curve_follow(section, ped, hpvx)
	return false unless ped
	pt = section.last
	return false unless pt
	pvx1 = hpvx[ped.pvx1_id]
	pvx2 = hpvx[ped.pvx2_id]
	pt1 = pvx1.target
	pt2 = pvx2.target
	if pt1 == pt
		section.push pt2
	elsif pt2 == pt
		section.push pt1
	end	
	
end

#CURVE: Generate the original curves
def geometry_transfer_original_curves(block)
	return unless @option_gen_group
	all_curves_ori = block.all_curves_ori
	return unless all_curves_ori && all_curves_ori.length > 0
	
	g = @group_entities.add_group
	gent = g.entities
	all_curves_ori.each { |lpt| gent.add_curve lpt }
	g.explode
end

#CURVE: Generate the new curves on the push-pulled geometry generated
def geometry_transfer_top_curves(block)
	all_curves_top = block.all_curves_top
	return unless all_curves_top && all_curves_top.length > 0
	
	entities = (@option_gen_group) ? @group_entities : @top_entities
	g = entities.add_group
	gent = g.entities
	all_curves_top.each { |lpt| gent.add_curve lpt }
	g.explode
end

#-----------------------------------------------------------
# EDGE_PROP: Manage the Edge properties
#-----------------------------------------------------------

#EDGE_PROP: Transfer Edge properties for top faces
def geometry_transfer_edge_prop_top(ped, hpvx, hsh_roundings, trblock_inv, force_thicken)
	#Edge at border
	if ped.at_border || @option_borders == :grid
		soft = smooth = hidden = false
		casts_shadows = true
	else	
		soft = ped.soft
		smooth = ped.smooth
		hidden = ped.hidden
		casts_shadows = ped.casts_shadows
		return if soft && smooth && !hidden && casts_shadows
	end
	
	#Coordinates of the edge
	pvx1 = hpvx[ped.pvx1_id]
	pvx2 = hpvx[ped.pvx2_id]
	pt1 = trblock_inv * pvx1.target
	pt2 = trblock_inv * pvx2.target
	
	#Is the edge a Rounding?
	ped_rounding = (@jpp_mode == :round) ? hsh_roundings[ped.su_edge_id] != nil : false
	
	#Finding the face with a corresponding edge for the top face
	hsh_edge_used = {}
	done = false
	ped.pface.gen_info.each do |info|
		new_face, code = info
		next if code != :t
		new_face.edges.each do |edge|
			edge_id = edge.entityID
			next if hsh_edge_used[edge_id]
			hsh_edge_used[edge_id] = true
			
			pv1 = edge.start.position
			pv2 = edge.end.position
			if (pv1 == pt1 && pv2 == pt2) || (pv1 == pt2 && pv2 == pt1)
				if ped_rounding
					edge.smooth = false
					edge.soft = true
				else	
					edge.soft = soft
					edge.smooth = smooth
					edge.hidden = hidden
					edge.casts_shadows = casts_shadows
				end	
				done = true
				break
			end	
		end
		break if done
	end		
	
	#Finding the face with a corresponding edge for the original face when in group
	if @option_gen_group && (@option_thickening || force_thicken) 
		pt1 = trblock_inv * pvx1.origin
		pt2 = trblock_inv * pvx2.origin
		hsh_edge_used = {}
		ped.pface.gen_info.each do |info|
			new_face, code = info
			next if code != :o
			new_face.edges.each do |edge|
				edge_id = edge.entityID
				next if hsh_edge_used[edge_id]
				hsh_edge_used[edge_id] = true
				
				pv1 = edge.start.position
				pv2 = edge.end.position
				if (pv1 == pt1 && pv2 == pt2) || (pv1 == pt2 && pv2 == pt1)
					if ped_rounding
						edge.smooth = false
						edge.soft = true
					else	
						edge.soft = soft
						edge.smooth = smooth
						edge.hidden = hidden
						edge.casts_shadows = casts_shadows
					end	
					return
				end	
			end
		end		
	end
end

#EDGE_PROP: Transfer Edge properties for borders
def geometry_transfer_edge_prop_border(ped, hpvx, trblock_inv, hsh_pvx_used)
	return unless ped.at_border || @option_borders == :grid
	
	pface = ped.pface		
	pvx1 = hpvx[ped.pvx1_id]
	pvx2 = hpvx[ped.pvx2_id]
	
	[pvx1, pvx2].each do |pvx|
		pvx_id = pvx.vx_id
		next if hsh_pvx_used[pvx_id]
		hsh_pvx_used[pvx_id] = true
		origin = trblock_inv * pvx.origin
		target = trblock_inv * pvx.target
		geometry_edge_prop_border_at_vx(pface, pvx, origin, target)
	end
end

#EDGE_PROP: Analyze border at a given vertex of the ped
def geometry_edge_prop_border_at_vx(pface, pvx, origin, target)
	vx0 = pvx.vx
	vec_border = origin.vector_to target
	return unless vx0.valid?
	
	#Finding the face with a corresponding edge
	pface.gen_info.each do |info|
		new_face, code = info
		next if code != :b
		
		new_face.vertices.each do |vxt|
			next unless vxt.position == target
			return if vxt.position == origin
			return if vxt.curve_interior?
			
			#Edge for the border
			edge = vxt.edges.find { |e| e.other_vertex(vxt).position == origin }
				
			#Finding a original edge which is possibly parallel
			if vx0.edges.length == 2
				edge_guide = nil
				vx0.edges.each do |e|
					vec_e = e.start.position.vector_to e.end.position
					if vec_e.parallel?(vec_border)
						edge_guide = e
						break
					end
				end	
			end
			
			#Edge property based on edge guide or just plain edge
			return if G6.edge_coplanar?(edge)
			if pvx.dashed || (@auto_soften_ps && edge.faces.length == 2)
				if @auto_soften_ps && (edge.faces.first.normal % edge.faces.last.normal).abs < @auto_soften_ps
					edge.soft = edge.smooth = false
				end	
			elsif edge_guide
				edge.soft = edge_guide.soft?
				edge.smooth = edge_guide.smooth?
				edge.hidden = edge_guide.hidden?
				edge.casts_shadows = edge_guide.casts_shadows?
			else
				edge.soft = edge.smooth = false
			end	
			if !edge.soft? || !edge.smooth?
				@top_entities.add_line edge.start.position, edge.end.position unless @option_gen_group
			end
			return
		end
	end		
end

#EDGE_PROP: Transfer Edge properties for borders
def geometry_transfer_edge_prop_junction(junction, hpvx, trblock_inv, nbj)
	return if nbj != 1
	pvx1 = junction.pvx1
	pvx2 = junction.pvx2
	ptsj = junction.lst_pts_edge.collect { |pt| trblock_inv * pt }
	origin = pvx1.origin
	pt1 = pvx1.target
	pt2 = pvx2.target
	ptsb = [origin, pt1, pt2]
	
	#Edge properties to be set
	soft = (nbj == 1) ? false : true
	
	#Setting the edge prop of the edges from the bordering faces
	[pvx1, pvx2].each do |pvx|
		pface = pvx.pface
		pface.gen_info.each do |info|
			new_face, code = info
			if code == :jr
				new_face.edges.each do |edge|
					if ptsj.include?(edge.start.position) && ptsj.include?(edge.end.position)
						edge.smooth = false
						edge.soft = soft
					end	
				end
			elsif nbj == 1 && code == :jb
				new_face.edges.each do |edge|
					if ptsb.include?(edge.start.position) && ptsb.include?(edge.end.position)
						edge.smooth = false
						edge.soft = true
					end	
				end
			end	
		end
	end
end

#-----------------------------------------------------------
# COPLANAR: Manage the removal of coplanar edges
#-----------------------------------------------------------

#COPLANAR: Handle coplanar edges on generated faces and borders
def geometry_coplanar_edges(pface, hsh_edges_erase)
	#Building hash of faces for the top
	hnewfaces_top = {}
	pface.gen_info.each do |info|
		new_face, code, val = info
		nfid = new_face.entityID
		case code
		when :t, :o
			hnewfaces_top[nfid] = new_face
		end
	end	
		
	#Checking edges with 2 coplanar faces
	hsh_edges_used = {}
	dotmax = 0.9999999991	#based on thomthom advice
	hnewfaces_top.each do |nfid, face|
		face.edges.each do |edge|
			edge_id = edge.entityID
			next if hsh_edges_used[edge_id]
			next if hsh_edges_erase.has_key?(edge_id)
			lf = edge.faces.find_all { |f| hnewfaces_top[f.entityID] } 
			hsh_edges_used[edge_id] = true
			next if lf.length != 2
			dot = lf.first.normal % lf.last.normal
			hsh_edges_erase[edge_id] = (dot.abs > dotmax) ? edge : false
		end
	end
end

#COPLANAR: Handle coplanar edges and potential overhangs on generated faces and borders
def geometry_coplanar_edges_overhang(pface, hsh_edges_erase, hpvx, hped, trblock_inv)
	#Building hash of faces for the top
	hnewfaces_top = {}
	pface.gen_info.each do |info|
		new_face, code, val = info
		nfid = new_face.entityID
		case code
		when :t, :o
			hnewfaces_top[nfid] = new_face
		end
	end	
	
	#Analyzing edges of face
	hsh_edge_border = {}
	border_pts = []
	face = pface.face
	face.edges.each do |edge|
		edge_id = edge.entityID
		next if hsh_edge_border[edge_id]
		ped_id = @proc_key_pseudo.call(edge_id, face.entityID)
		ped = hped[ped_id]
		next unless ped
		hsh_edge_border[edge_id] = true
		pt1 = trblock_inv * hpvx[ped.pvx1_id].target
		pt2 = trblock_inv * hpvx[ped.pvx2_id].target
		border_pts.push [pt1, pt2]
	end	
	
	#Checking edges with 2 coplanar faces
	hsh_edges_used = {}
	lst_edges_erase = []
	dotmax = 0.9999999991	#based on thomthom advice
	hnewfaces_top.each do |nfid, face|
		face.edges.each do |edge|
			edge_id = edge.entityID
			next if hsh_edges_used[edge_id]
			next if hsh_edge_border[edge_id]
			next if hsh_edges_erase.has_key?(edge_id)
			hsh_edges_used[edge_id] = edge
			lf = edge.faces.find_all { |f| hnewfaces_top[f.entityID] } 
			next if lf.length != 2
			dot = lf.first.normal % lf.last.normal
			lst_edges_erase.push edge if dot.abs > dotmax
		end
	end
	return if lst_edges_erase.empty?
	
	#Handling overhang
	llsuspects = []
	llerase = []
	lst_edges_erase.each do |edge|
		lpt = geometry_edge_crosses_face?(edge, border_pts)
		if lpt
			llsuspects.push lpt
		else
			llerase.push edge
		end	
	end	
	
	#No overhang - just erasing coplanar edges	
	return @group_entities.erase_entities(llerase) if llsuspects.empty?
		
	#Getting the edge properties of the border
	ledge_by_prop = []
	lines = border_pts.clone
	i = 0
	while lines.length > 0
		line = lines.shift
		hsh_edges_used.each do |edge_id, edge|
			pt1 = edge.start.position
			pt2 = edge.end.position
			if line.include?(pt1) && line.include?(pt2)
				ledge_by_prop[i] = edge
				break
			end
		end
		i += 1
	end	
	
	#Overhang: recreating the target border		
	g = @group_entities.add_group
	edges = []
	border_pts.each { |lpt| edges.push g.entities.add_line(lpt) }
	edges.each_with_index do |e, i|
		ee = ledge_by_prop[i]
		e.smooth = ee.smooth?
		e.soft = ee.soft?
		e.casts_shadows = ee.casts_shadows?
		e.hidden = ee.hidden?
	end	
	g.explode
	
	#Erasing the collinear edges
	llsuspects.each { |lpt| llerase += @group_entities.add_edges(lpt) }
	@group_entities.erase_entities llerase.find_all { |e| e.valid? }
end

#Check if the edge crosses the border of the face
def geometry_edge_crosses_face?(edge, border_pts)
	pt1 = edge.start.position
	pt2 = edge.end.position
	seg = [pt1, pt2]
	border_pts.each do |line|
		ptinter = G6.intersect_segment_segment(seg, line)
		return [pt1, ptinter, pt2] if ptinter && pt1 != ptinter && pt2 != ptinter			
	end	
	nil
end

#-----------------------------------------------------------
# MATERIAL: Manage the creation of roundings
#-----------------------------------------------------------

#MATERIAL: Transfer material and textures for the top face (and base face if gen_group)
def geometry_transfer_material_top(pface, hpvx, trblock_inv, reversal)
	ori_face = pface.face
	
	#Materials for the original face
	mat0 = ori_face.material
	bk_mat0 = ori_face.back_material
	return if !mat0 && !bk_mat0 && !reversal 
	
	uvh = ori_face.get_UVHelper(true, true, @tw)
	pface.gen_info.each do |info|
		new_face, code, itri = info
		next if code != :t && code != :o
		triangle = pface.ls_triangles[itri]
		lpt_ori = triangle.collect { |id| trblock_inv * hpvx[id].origin }
		lpt_new = (code == :o) ? lpt_ori : triangle.collect { |id| trblock_inv * hpvx[id].target }
		
		#Font side
		if mat0 && mat0.texture
			lsuv = lpt_ori.collect { |pt| uv = uvh.get_front_UVQ(pt) ; Geom::Point3d.new(uv.x / uv.z, uv.y / uv.z, 1) }
			lpos_uv_front = [lpt_new[0], lsuv[0], lpt_new[1], lsuv[1], lpt_new[2], lsuv[2]]
		end

		#Back side
		if bk_mat0 && bk_mat0.texture
			lsuv = lpt_ori.collect { |pt| uv = uvh.get_back_UVQ(pt) ; Geom::Point3d.new(uv.x / uv.z, uv.y / uv.z, 1) }
			lpos_uv_back = [lpt_new[0], lsuv[0], lpt_new[1], lsuv[1], lpt_new[2], lsuv[2]]
		end
		
		#Reversing the faces
		new_face.reverse! if (reversal == :top && code == :t) || (reversal == :base && code == :o)
		
		#Transfering the texture
		if mat0 && mat0.texture
			new_face.position_material mat0, lpos_uv_front, true
		elsif mat0
			new_face.material = mat0
		end

		#Back side
		if bk_mat0 && bk_mat0.texture
			new_face.position_material bk_mat0, lpos_uv_back, false		
		elsif bk_mat0
			new_face.back_material = bk_mat0		
		end
		
	end	
end

#MATERIAL: Transfer material and textures for the top face (and base face if gen_group)
def geometry_transfer_material_borders(pface, hpvx, trblock_inv, reversal, force_thicken)
	ori_face = pface.face
	
	#Materials for the original face
	mat0 = ori_face.material
	bk_mat0 = ori_face.back_material
	
	uvh = ori_face.get_UVHelper(true, true, @tw)
	pface.gen_info.each do |info|
		new_face, code, itri, matinfo = info
		next if code != :b
		matinfo = nil if !@option_color_border_adjacent && (@option_thickening || force_thicken)
		
		#Font side
		if matinfo && matinfo[0]
			new_face.material = matinfo[0]	
		elsif mat0
			new_face.material = mat0
		end

		#Back side
		if matinfo && matinfo[1]
			new_face.back_material = matinfo[1]	
		elsif bk_mat0
			new_face.back_material = bk_mat0
		end	
	end	
end

#MATERIAL: Transfer material and textures for the top face (and base face if gen_group)
def geometry_transfer_material_roundings(pface, hpvx, trblock_inv, reversal)
	ori_face = pface.face
	
	#Materials for the original face
	mat0 = ori_face.material
	bk_mat0 = ori_face.back_material
	#return if !mat0 && !bk_mat0 
	
	uvh = ori_face.get_UVHelper(true, true, @tw)
	pface.gen_info.each do |info|
		new_face, code, itri = info
		next unless [:jr, :jb, :cr].include?(code)
		
		#Font side
		if mat0
			new_face.material = mat0
		end

		#Back side
		if bk_mat0
			new_face.back_material = bk_mat0
		end	
	end	
end

#-----------------------------------------------------------
# ROUNDING: Manage the creation of roundings
#-----------------------------------------------------------

#ROUNDING: Create the mesh for the roundings
def geometry_create_roundings(block, geninfo, mesh, trblock_inv)
	hpfaces = block.hpfaces
	hpvx = block.hsh_pvx
	ipos = (@offset > 0) ? 0 : 1
	hsh_roundings = block.hsh_roundings[ipos]
	hhj = block.hsh_junctions_vx[ipos]
	hped_used = {}
	hrounding_used = {}
	
	#Constructing the chains of junctions
	hsh_roundings.each do |edge_id, rounding|
		next if hrounding_used[edge_id]
		hrounding_used[edge_id] = rounding
		rd_chain1 = geometry_next_rounding(ipos, hhj, rounding.junction1, hsh_roundings, hped_used, hrounding_used)
		rd_chain2 = geometry_next_rounding(ipos, hhj, rounding.junction2, hsh_roundings, hped_used, hrounding_used)
		rd_chain = rd_chain1.reverse + [rounding] + rd_chain2		
		geometry_junction_pairing_along_chain rd_chain
	end
	
	#Calculating the profiles of junctions: different calculation for tri-corners and other junctions
	hhj.each do |id, junctions|
		if junctions.length == 3
			geometry_junction_profiles_3(junctions)	
		else
			junctions.each { |junction| junction.lst_pts_edge = junction_calculate_points(junction, junction.nb_seg) }
		end	
	end
	
	#Constructing the mesh for the roundings
	hsh_roundings.each do |edge_id, rounding|
		geometry_rounding_mesh(rounding, geninfo, mesh, trblock_inv, hpfaces)
	end	
	
	#Constructing the mesh for the base and corners of junctions
	hhj.each do |id, junctions|
		case junctions.length
		when 1
			geometry_junction_base_mesh(junctions[0], geninfo, mesh, trblock_inv, hpfaces, hpvx)
		when 2
			#do nothing - no junction created
		when 3
			geometry_corner_mesh_3(junctions, geninfo, mesh, trblock_inv, hpfaces, hpvx)
		else
			geometry_corner_mesh(junctions, geninfo, mesh, trblock_inv, hpfaces, hpvx)
		end	
	end
end

#ROUNDING: Extend a chain of roundings from a junction
def geometry_next_rounding(ipos, hhj, junction, hsh_roundings, hped_used, hrounding_used)
	chain = []
	while true
		return chain unless junction
		vx = junction.pvx1.vx
		vx_id = vx.entityID
		junctions_at_vx = hhj[vx_id]
		
		#Finding the pairing junction
		other_junction = nil	
		case junctions_at_vx.length
		when 2
			other_junction = (junctions_at_vx[0] == junction) ? junctions_at_vx[1] : junctions_at_vx[0]
		when 3
			other_junction = geometry_rank_junctions_3(junction, junctions_at_vx)
		when 4
			other_junctions = junctions_at_vx.find_all { |j| j != junction && !j.used }
			case other_junctions.length
			when 1
				other_junction = other_junctions[0]
			when 3 
				vec1 = junction.vec1
				vec2 = junction.vec2
				other_junction = other_junctions.find { |j| j.vec1 != vec1 && j.vec1 != vec2 && j.vec2 != vec1 && j.vec2 != vec2 }
			end	
		end
		return chain unless other_junction
		
		#Pairing the junctions
		junction.friend = other_junction
		other_junction.friend = junction
		
		#Registering the Next rounding
		ped = other_junction.ped
		edge = ped.edge
		edge_id = edge.entityID
		rounding = hsh_roundings[edge_id]
		return chain if hrounding_used[edge_id]
		
		#Inserting the rounding in the chain
		chain.push rounding
		hrounding_used[edge_id] = rounding
		rounding.junction1.used = true if rounding.junction1
		rounding.junction2.used = true if rounding.junction2
		
		#Getting the next junction
		junction = (ped.junction1[ipos] == other_junction) ? ped.junction2[ipos] : ped.junction1[ipos]
	end
end

#ROUNDING: Find pairing junctions at a tri-corner
def geometry_rank_junctions_3(junction, junctions_at_vx)
	other_junctions = junctions_at_vx.find_all { |j| j != junction && !j.used }
	return nil if other_junctions.length < 2
	j1 = other_junctions.first
	j2 = other_junctions.last
	
	#Vector for the junctions
	vec = junction.rounding.vector
	vec1 = j1.rounding.vector
	vec2 = j2.rounding.vector
	ps1 = (vec1 % vec).abs
	ps2 = (vec2 % vec).abs
	
	#Privileging continuity
	if ps1 == ps2
		ps1 = ((vec * vec1).normalize % Z_AXIS).abs
		ps2 = ((vec * vec2).normalize % Z_AXIS).abs
		ps12 = ((vec1 * vec2).normalize % Z_AXIS).abs
		if ps1 > 0.9 && ps2 > 0.9
			return nil
		elsif ps2 > 0.9
			return j2
		else
			return j1
		end	
	end
	other_junction = (ps1 > ps2) ? j1 : j2
end

#ROUNDING: Computing the points for a junction
def geometry_junction_pairing_along_chain(rd_chain)
	lst_junctions = []
	rd_chain.each do |rounding|
		lst_junctions.push rounding.junction1 if rounding.junction1
		lst_junctions.push rounding.junction2 if rounding.junction2
	end

	anglemax = 0
	lst_junctions.each do |junction|
		junction.angle = angle = junction.vec1.angle_between(junction.vec2)
		anglemax = angle if angle > anglemax
	end
	
	#Computing the nb of segments
	n180 = 2 * @segment_round
	n = ((anglemax / Math::PI) * n180).round
	n += 1 if n.modulo(2) != 0
	
	#Computing the arc points for each junction
	lst_junctions.each { |junction| junction.nb_seg = n }
end

#ROUNDING: Calculate the profile of junctions for Tri-corners
def geometry_junction_profiles_3(junctions)
	junctions.each do |junction|
		target1 = junction.pvx1.target
		target2 = junction.pvx2.target
		j1 = junctions.find { |j| j != junction && (j.pvx1.target == target1 || j.pvx2.target == target1) }
		j2 = junctions.find { |j| j != junction && (j.pvx1.target == target2 || j.pvx2.target == target2) }
		puts "NO J1 J2" if !j1 && !j2
		
		#Determining anchor for Side 1 of junction
		r1 = j1.rounding
		oj1 = (r1.junction1 == j1) ? r1.junction2 : r1.junction1
		next unless oj1
		otarget1 = (target1 == j1.pvx1.target) ? oj1.pvx1.target : oj1.pvx2.target
		vec1 = target1.vector_to otarget1
		
		#Determining anchor for Side 2 of junction
		r2 = j2.rounding
		oj2 = (r2.junction1 == j2) ? r2.junction2 : r2.junction1
		next unless oj2
		otarget2 = (target2 == j2.pvx1.target) ? oj2.pvx1.target : otarget2 = oj2.pvx2.target
		vec2 = target2.vector_to otarget2
		
		#Calculating the profile
		junction.lst_pts_edge = junction_compute_profile(junction.nb_seg, target1, vec1, target2, vec2)
	end
end

#ROUNDING: Generate the mesh faces for the rounding
def geometry_rounding_mesh(rounding, geninfo, mesh, trblock_inv, hpfaces)
	#Points for the rounding and its junctions
	junction1 = rounding.junction1
	junction2 = rounding.junction2
	return unless junction1 || junction2
	pface1 = hpfaces[rounding.face1_id]
	pface2 = hpfaces[rounding.face2_id]
	ped_id = rounding.edge_id
	n = (junction1) ? junction1.lst_pts_edge.length : junction2.lst_pts_edge.length
	if junction1
		pts1 = junction1.lst_pts_edge.collect { |pt| trblock_inv * pt }
	else
		pt = trblock_inv * rounding.pvx1.target
		pts1 = Array.new n, pt
	end
	if junction2
		pts2 = junction2.lst_pts_edge.collect { |pt| trblock_inv * pt }
	else
		pt = trblock_inv * rounding.pvx2.target
		pts2 = Array.new n, pt
	end
	
	#Calculating the line vectors (used for corners)
	jv1 = junction1.line_vec = [] if junction1
	jv2 = junction2.line_vec = [] if junction2
	for i in 0..n-1
		vec = pts1[i].vector_to pts2[i]
		jv1[i] = vec if junction1
		jv2[i] = vec.reverse if junction2
	end
	
	#Managing the orientation
	edge = rounding.pvx1.vx.common_edge rounding.pvx2.vx
	reverse = edge.reversed_in?(pface1.face)
	rounding.reverse = reverse

	unless reverse
		pts1 = pts1.reverse
		pts2 = pts2.reverse
		a = pface1
		pface1 = pface2
		pface2 = a
	end	

	junction1.reverse = !reverse
	junction2.reverse = reverse
	
	#Creating the mesh for the rounding based on 2 half-cylinders
	n2 = n / 2
	for j in 0..n-2
		pface = (j < n2) ? pface1 : pface2
		pts = [pts1[j], pts2[j], pts2[j+1], pts1[j+1]]
		geometry_quad_mesh(pts, mesh, geninfo, pface, :jr, ped_id)
	end
end

#ROUNDING: Generate the mesh faces for the rounding
def geometry_junction_base_mesh(junction, geninfo, mesh, trblock_inv, hpfaces, hpvx)
	return if @option_borders == :none
	
	#Initialization
	pts = junction.lst_pts_edge.collect { |pt| trblock_inv * pt }
	jpvx1 = junction.pvx1
	jpvx2 = junction.pvx2
	origin = jpvx1.origin
	pface1 = hpfaces[junction.face1_id]
	pface2 = hpfaces[junction.face2_id]
	
	#Computing the right orientation
	ped = junction.ped
	vx = junction.pvx1.vx
	epvx1 = hpvx[ped.pvx1_id]
	epvx2 = hpvx[ped.pvx2_id]
	normal = epvx1.origin.vector_to epvx2.origin
	normal = normal.reverse if epvx1.vx == jpvx1.vx
	vec1 = origin.vector_to jpvx1.target
	vec2 = origin.vector_to jpvx2.target
	reverse = ((vec1 * vec2) % normal < 0)
	
	lpt = pts + [trblock_inv * origin]
	lpt = lpt.reverse if reverse
	
	#Creating 2 faces for each side-faces
	origin = trblock_inv * origin
	n = pts.length / 2
	
	lpt = pts[0..n] + [origin]
	lpt = lpt.reverse if reverse
	mesh.add_polygon lpt
	geninfo.push [pface1, [:jb, 0, 0]]
	
	lpt = pts[n..-1] + [origin]
	lpt = lpt.reverse if reverse
	mesh.add_polygon lpt
	geninfo.push [pface2, [:jb, 0, 0]]
end

#ROUNDING: Generate the corners
def geometry_corner_mesh_3(junctions, geninfo, mesh, trblock_inv, hpfaces, hpvx)
	#Determining the junctions to pair
	jfriends = geometry_find_friend_junctions(junctions)
	unless jfriends
		puts "FRIENDS not found====================================="
		return
	end
	junction1, junction2 = jfriends
	
	#Finding the guiding junction
	other_junctions = junctions.find_all { |j| j != junction1 && j != junction2 }
	oj = other_junctions[0]
	return unless oj

	#Points of profiles and directions
	jv1 = junction1.line_vec
	jv2 = junction2.line_vec
	pts1 = junction1.lst_pts_edge
	pts2 = junction2.lst_pts_edge

	#Reversing the profiles as needed
	reversed = junction1.reverse
	reverse2 = reverse1 = false
	if pts1.first == pts2.last
		pts2 = pts2.reverse
		jv2 = jv2.reverse
		reverse2 = !reverse2
	elsif pts1.last == pts2.first
		pts1 = pts1.reverse
		jv1 = jv1.reverse
		reversed = !reversed
		reverse1 = !reverse1
	elsif pts1.last == pts2.last
		pts1 = pts1.reverse
		jv1 = jv1.reverse
		pts2 = pts2.reverse
		jv2 = jv2.reverse
		reversed = !reversed
		reverse1 = !reverse1
		reverse2 = !reverse2
	end	
	
	#Guiding profiles on the border by the other junction(s)
	opts2 = oj.lst_pts_edge
	opts2 = opts2.reverse if opts2.last == pts1.last
	nb_seg = opts2.length - 1
	opts1 = Array.new nb_seg+1, pts1.first	
	
	#Constructing the junction lines
	pts1 = pts1.collect { |pt| trblock_inv * pt }
	pts2 = pts2.collect { |pt| trblock_inv * pt }
	opts1 = opts1.collect { |pt| trblock_inv * pt }
	opts2 = opts2.collect { |pt| trblock_inv * pt }
	
	lbz = [opts1]
	npts = pts1.length-1
	for i in 1..npts-1
		bz = junction_compute_profile(nb_seg, pts1[i], jv1[i], pts2[i], jv2[i])
		lbz[i] = bz
	end
	lbz.push opts2

	#Calculating the influencing faces
	if reverse1
		pface11 = hpfaces[junction1.face2_id]
		pface12 = hpfaces[junction1.face1_id]
	else
		pface11 = hpfaces[junction1.face1_id]
		pface12 = hpfaces[junction1.face2_id]
	end
	if reverse2
		pface21 = hpfaces[junction2.face2_id]
		pface22 = hpfaces[junction2.face1_id]
	else
		pface21 = hpfaces[junction2.face1_id]
		pface22 = hpfaces[junction2.face2_id]
	end
	
	#Creating the faces for the mesh
	nb_seg2 = nb_seg / 2
	n2 = lbz.length / 2
	nz = lbz.length - 2
	for i in 0..nz
		bz1 = lbz[i]
		bz2 = lbz[i+1]
		for j in 0..nb_seg-1
			if i < n2
				pface = (j < nb_seg2) ? pface11 : pface21
			else
				pface = (j < nb_seg2) ? pface12 : pface22
			end
			quad = [bz1[j], bz2[j], bz2[j+1], bz1[j+1]]
			quad = quad.reverse if reversed
			geometry_quad_mesh(quad, mesh, geninfo, pface, :cr, 0)
		end	
	end
end

#ROUNDING: Determine two friend junctions
def geometry_find_friend_junctions(junctions)
	n = junctions.length - 1
	for i in 0..n
		junc_i = junctions[i]
		for j in i+1..n
			junc_j = junctions[j]
			if junc_i.friend == junc_j || junc_j.friend == junc_i
				return [junc_i, junc_j]
			end
		end	
	end
	nil
end

#ROUNDING: Generate the corners
def geometry_corner_mesh(junctions, geninfo, mesh, trblock_inv, hpfaces, hpvx)
	#Calculating the pivot point
	origin = junctions[0].pvx1.origin
	lvec = []
	lpt = []
	heigth_avg = 0
	junctions.each do |junction|
		lvec.push junction.vec1, junction.vec2
		lpt.push junction.pvx1.target, junction.pvx2.target
		heigth_avg += 0.5 * (origin.distance(junction.pvx1.target) + origin.distance(junction.pvx2.target))
	end
	heigth_avg /= junctions.length
	pt_pivot = geometry_barycenter(lpt)
	vec_pivot = origin.vector_to pt_pivot
	pt_pivot = origin.offset vec_pivot, heigth_avg
	
	#Calculating the number of segments
	anglemax = 0
	junctions.each do |junction|
		angle = junction.vec1.angle_between vec_pivot
		anglemax = angle if angle > anglemax
	end
	n180 = 2 * @segment_round
	nb_seg = ((anglemax / Math::PI) * n180).round
	#nb_seg += 1 if nb_seg.modulo(2) != 0
	nb_seg = 1 if nb_seg == 0
	
	#Calculating the corner contribution by junction
	junctions.each do |junction|
		reversed = junction.reverse
		pface1 = hpfaces[junction.face1_id]
		pface2 = hpfaces[junction.face2_id]
		pts = junction.lst_pts_edge
		lbz = []
		pts.each_with_index do |pt, i|
			vec1 = origin.vector_to pt
			normal = vec1 * vec_pivot
			angle = vec1.angle_between(vec_pivot) / nb_seg
			lpt = [pt]
			for i in 1..nb_seg-1
				t = Geom::Transformation.rotation origin, normal, i * angle
				lpt.push t * pt
			end	
			lpt.push pt_pivot
			lbz.push lpt
		end
		
		n2 = lbz.length / 2
		for i in 0..lbz.length-2
			bz1 = lbz[i]
			bz2 = lbz[i+1]
			pface = (i < n2) ? pface1 : pface2
			for j in 0..bz1.length-2
				quad = [bz1[j], bz2[j], bz2[j+1], bz1[j+1]]
				quad = quad.reverse if reversed
				geometry_quad_mesh(quad, mesh, geninfo, pface, :cr, 0)
			end
		end
	end
	
end

#GEOMETRY: Compute the barycenter of a sequence of points
def geometry_barycenter(lpt)
	x = y = z = 0
	lpt.each do |pt|
		x += pt.x
		y += pt.y
		z += pt.z
	end
	n = lpt.length
	Geom::Point3d.new x / n, y / n, z / n
end

#GEOMETRY: Create the faces in the mesh for a quad
def geometry_quad_mesh(quad, mesh, geninfo, pface, code, extra=nil)
	return if quad.length < 3
	
	#Triangular faces
	if quad.length == 3
		mesh.add_polygon quad
		geninfo.push [pface, [code, nil, extra]]
		return
	end
	
	#Quad faces
	pts = [quad[0]]
	for i in 1..3
		pts.push quad[i] unless quad[i] == quad[i-1]
	end	
	pts = pts[0..2] if pts.first == pts.last
	return if pts.length < 3
	
	#Triangular face
	if pts.length == 3
		begin
			mesh.add_polygon pts
			geninfo.push [pface, [code, nil, extra]]
			return
		rescue
			puts "BAD QUAD PTS #{quad.inspect} - PTS = #{pts.inspect}" if pts.find { |a| !a }
		end
	end
	
	#Quad face, planar or non planar
	pts02 = pts[0..2]
	plane = Geom.fit_plane_to_points(pts02)
	if pts.length == 3 || (plane && pts[3].on_plane?(plane))
		mesh.add_polygon pts
		geninfo.push [pface, [code, nil, extra]]
	else
		mesh.add_polygon pts02
		geninfo.push [pface, [code, 0, extra]]
		mesh.add_polygon [pts[2], pts[3], pts[0]]
		geninfo.push [pface, [code, 1, extra]]
	end
end

end	#class JointPushPullTool

end	#End Module F6_JointPushPull
