=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed June 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			:   JointPushPullAlgo.rb
# Original Date	:   15 Jun 2013
# Description	:   JointPushPull Interactive Tool
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_JointPushPull

#=============================================================================================
#=============================================================================================
# Class JointPushPullTool: main class for the Interactive tool
#=============================================================================================
#=============================================================================================

class JointPushPullTool < Traductor::PaletteSuperTool

#STRUCT: Initialization of working structures
Grouping = Struct.new :parent, :tr, :hfaces, :lst_blocks, :tr_slaves

Block = Struct.new :parent, :tr, :tr_inv, :hpfaces, :hsh_pvx, :hsh_ped, :lst_pfaces, :centroid, 
                   :centroid_target, :vector_extrude, :grouping, :lst_pholes,
				   :reversal, :planar_dir, :hsh_junctions_vx, :lst_junctions, :hsh_roundings,
				   :shield_pfaces, :shield_hpvx, :shield_peds, :hwireframe_faces, :hwireframe_borders,
				   :hwireframe_edges, :lst_peds, :hsh_edge_protected, :force_thicken,
				   :hcurves, :all_curves_ori, :all_curves_top
				   
PseudoFace = Struct.new :face_id, :face, :normal, :ls_triangles, :mtriangles, :reversed,
                        :area, :hole, :gen_info, :color, :layer, :material, :back_material,
						:rand_factor, :coquad, :initial

PseudoVertex = Struct.new :vx_id, :vx, :pface, :origin, :vec, :factor, :dashed,
                          :target, :at_border, :junction, :cofacing, :lpeds, :ref_radial

PseudoEdge = Struct.new :ed_id, :su_edge_id, :edge, :pface, :pvx1_id, :pvx2_id, :at_border, :matinfo,
                        :junction1, :junction2, :color, :layer, :dashed, :coplanar,
						:soft, :smooth, :hidden, :casts_shadows, :coface, :curve

PseudoHole = Struct.new :pface, :iloop

Junction = Struct.new :ipos, :origin, :pvx1, :pvx2, :vec1, :vec2, :face1_id, :face2_id, :ped, :iped,
                      :lst_vec, :angle, :lst_pts_edge, :rounding, :used, :friend, :line_vec, :nb_seg,
					  :reverse

Rounding = Struct.new :edge_id, :junction1, :junction2, :face1_id, :face2_id, :pvx1, :pvx2, :reverse,
                      :vector

Coface = Struct.new :face, :face_id

#--------------------------------------------------
# PSEUDO: Create and manage Pseudo structures
#--------------------------------------------------

#PSEUDO: Create a PseudoFace structure
def pseudoface_create(face, reversed=false)
	#Creating the structure
	pface = PseudoFace.new
	pface.face_id = face_id = face.entityID
	pface.face = face
	pface.layer = face.layer
	pface.material = face.material
	pface.back_material = face.back_material
	pface.reversed = reversed
	pface.normal = (reversed) ? face.normal.reverse : face.normal
	@hsh_pseudofaces[face_id] = pface
	pface.initial = true if face == @initial_face
	
	#Calculating the triangular mesh of the face (id reference)
	lvx = face.vertices
	
	#Computing the triangles for the face
	if lvx.length == 3
		triangles = [lvx.collect { |vx| @proc_key_pseudo.call(vx.entityID, face_id) }]
	else
		mesh = face.mesh
		pts = mesh.points
		llvx = []
		pts.each do |pt|
			vx = lvx.find { |vx| vx.position == pt } 
			llvx.push @proc_key_pseudo.call(vx.entityID, face_id) if vx
		end	
		triangles = []
		mesh.polygons.each do |p|
			triangles.push p.collect { |i| llvx[i.abs-1] }
		end
	end	
	pface.ls_triangles = triangles
	pface.mtriangles = triangles.flatten
	
	pface	
end

#PSEUDO: Create a PseudoVertex structure
def pseudovertex_create(vx, vx_id, pface, tr)
	pvx = PseudoVertex.new
	pvx.vx = vx
	pvx.vx_id = vx_id
	pvx.pface = pface
	pvx.origin = tr * vx.position
	pvx.factor = 1.0
	@hsh_pseudovx[vx_id] = pvx
	pvx
end

#PSEUDO: Create a PseudoVertex structure
def pseudoedge_create(edge, ped_id, pface, hpvx)
	ped = PseudoEdge.new
	ped.edge = edge
	ped.pface = pface
	ped.ed_id = ped_id
	ped.su_edge_id = edge.entityID
	@hsh_pseudoedges[ped_id] = ped
	face = pface.face
	face_id = face.entityID
	if edge.reversed_in?(face)
		ped.pvx1_id = @proc_key_pseudo.call(edge.end.entityID, face_id)
		ped.pvx2_id = @proc_key_pseudo.call(edge.start.entityID, face_id)
	else
		ped.pvx1_id = @proc_key_pseudo.call(edge.start.entityID, face_id)
		ped.pvx2_id = @proc_key_pseudo.call(edge.end.entityID, face_id)
	end
	
	pvx1 = hpvx[ped.pvx1_id]
	lpeds = pvx1.lpeds
	lpeds = pvx1.lpeds = [ ] unless lpeds
	lpeds.push ped
	
	pvx2 = hpvx[ped.pvx2_id]
	lpeds = pvx2.lpeds
	lpeds = pvx2.lpeds = [ ] unless lpeds
	lpeds.push ped
	
	#Edge properties
	ped.coplanar = G6.edge_coplanar?(edge)
	ped.layer = edge.layer
	ped.soft = edge.soft?
	ped.smooth = edge.smooth?
	ped.hidden = edge.hidden?
	ped.casts_shadows = edge.casts_shadows?
	
	ped
end

#PSEUDO: Create a PseudoHole structure
def pseudohole_create(pface, iloop)
	phole = PseudoHole.new
	phole.pface = pface
	phole.iloop = iloop
	phole
end

#----------------------------------------------------------------------
# REMAPPING: Manage the remapping of faces after an undo or abort
#----------------------------------------------------------------------

#REMAPPING: Remap faces from their ids 
def remapping_faces(flg_undo=false)
	#Constructing the overall mapping table for faces
	@hsh_master_faceid = {}
	@hsh_master_edgeid = {}
	@lst_groupings.each do |grouping|
		entities = G6.grouponent_entities grouping.parent
		lst_faces = entities.grep(Sketchup::Face)
		lst_faces.each { |face| @hsh_master_faceid[face.entityID] = face }
		lst_edges = entities.grep(Sketchup::Edge)
		lst_edges.each { |edge| @hsh_master_edgeid[edge.entityID] = edge }
	end
	
	#Remapping all faces 
	@hsh_pseudofaces.each do |face_id, pface|
		pface.face = @hsh_master_faceid[face_id]	
	end

	#Remapping all edges 
	@hsh_pseudoedges.each do |ped_id, ped|
		ped.edge = @hsh_master_edgeid[ped.su_edge_id]
		ped.coface.face = @hsh_master_faceid[ped.coface.face_id] if ped.coface
	end

	#Remapping all vertices
	@hsh_pseudofaces.each do |face_id, pface|
		face = pface.face
		face.vertices.each do |vx|
			vx_id = @proc_key_pseudo.call(vx.entityID, face_id)
			pvx = @hsh_pseudovx[vx_id]
			pvx.vx = vx if pvx
		end
	end
	
	#Remapping the underlying Facepicker
	if flg_undo
		@facepicker.selection_remapping_faces_when_undo @hsh_master_faceid
	else	
		@facepicker.selection_remapping_faces_when_geometry @hsh_master_faceid
	end	
	@initial_face, @tr, @parent = @facepicker.selection_get_picked_face_info
end

#---------------------------------------------------------------------------------------------
# SHIELD: Manage the visible faces offset to control inferences
#---------------------------------------------------------------------------------------------

#SHIELD: Compute the faces participating to the inference shield and shown in preview
def shield_select_candidates
	@hwireframe_colors = {}
	@option_color_border_adjacent = (@param_color_adjacent && @jpp_mode != :round)
	return unless @lst_blocks
	shield_compute_top_faces
	shield_compute_border_faces
end

#SHIELD: Compute the top faces for the shield (shown in preview)
def shield_compute_top_faces
	option_thickening = option_get(:thickening)
	nmax = @param_preview_faces
	n = 0
	@lst_blocks.each do |block|
		ls = block.shield_pfaces = []
		lsw_all = block.hwireframe_faces = {}
		hpvx = block.hsh_pvx
		shield_hpvx = block.shield_hpvx = {}
		thicken = option_thickening || block.force_thicken
		block.lst_pfaces.each do |pface|
			color = shield_face_color(pface, thicken)
			idcolor = color.object_id
			@hwireframe_colors[idcolor] = color
			ls.push pface
			pface.mtriangles.each { |id| shield_hpvx[id] = hpvx[id] }
			lsw = lsw_all[idcolor]
			lsw = lsw_all[idcolor] = [] unless lsw
			lsw.push *pface.mtriangles
			return if n > nmax
			n += 1
		end
	end
end

#SHEILD: compute the color of a face
def shield_face_color(pface, thicken, face=nil)
	face = pface unless face
	if !pface.reversed || thicken
		mat = face.material
		color = (mat) ? mat.color : @su_face_front_color
	else	
		mat = face.back_material
		color = (mat) ? mat.color : @su_face_back_color
	end	
	color
end

#SHIELD: Calculate the bordering faces
def shield_compute_border_faces
	option_borders = option_get(:borders)
	return if option_borders == :none
	option_gen_group = option_get(:gen_group)
	option_thickening = option_get(:thickening)
	nmax = @param_preview_borders
	n = 0
	@lst_blocks.each do |block|
		lsped_shield = block.shield_peds = []
		lsw_all = block.hwireframe_borders = {}
		lsb_all = block.hwireframe_edges = {}
		hpvx = block.hsh_pvx
		
		block.hsh_ped.each do |id_edge, ped|
			next unless ped.at_border || option_borders == :grid
			lsped_shield.push ped
			pface = ped.pface
			edge = ped.edge
			faces = edge.faces
			face = pface.face
			color = pface.color
			idcolor = color.object_id
			
			#Hide the natural edge if push pull can be continuous
			other_face = (ped.coface) ? ped.coface.face : nil
			thicken = option_thickening || block.force_thicken
			if other_face && !thicken && !option_gen_group
				color = shield_face_color(pface, thicken, other_face)
				pvx1 = hpvx[ped.pvx1_id]
				pvx2 = hpvx[ped.pvx2_id]
				idcolor = color.object_id
				lsb = lsb_all[idcolor]
				lsb = lsb_all[idcolor] = [] unless lsb
				lsb.push [pvx1.origin, pvx2.origin]
			elsif other_face && @option_color_border_adjacent
				color = shield_face_color(pface, thicken, other_face)
				idcolor = color.object_id
			else
				color = shield_face_color(pface, thicken, pface.face)
				idcolor = color.object_id
			end	
			
			#Storing the information by color
			@hwireframe_colors[idcolor] = color
			lsw = lsw_all[idcolor]
			lsw = lsw_all[idcolor] = [] unless lsw
			lsw.push ped

			#Stop when max number of border faces reached
			return if n > nmax
			n += 1
		end
	end	
end

#SHIELD: Check if the ray is occulted by the shield in case of inference
def shield_occulting?(view, ip)
	block_compute_all true

	#Checking if the Input Point is locked on one of the face
	vertex = ip.vertex
	edge = ip.edge
	iptra = ip.transformation.to_a	
	@lst_blocks.each do |block|
		next if iptra != block.tr.to_a
		block.lst_pfaces.each do |pface|
			face = pface.face
			return true if (vertex && vertex.used_by?(face)) || (edge && edge.used_by?(face))
		end
	end
	
	#Checking if the ray to the input point traverses an offset face
	target = ip.position
	eye = view.camera.eye
	ray = [eye, target]
	d0 = eye.distance(target) * 1.02
	@lst_blocks.each do |block|
		hpvx = block.hsh_pvx
		
		#Occulting by top faces
		block.shield_pfaces.each do |pface|
			pface.ls_triangles.each do |triangle|
				pts = triangle.collect { |id| hpvx[id].target }
				ptinter = G6.intersect_line_polygon(ray, pts)
				return true if ptinter && eye.distance(ptinter) <= d0
			end	
		end
		
		#Occulting by border faces
		unless option_get(:borders)
			block.shield_peds.each do |ped|
				pvx1 = hpvx[ped.pvx1_id]
				pvx2 = hpvx[ped.pvx2_id]
				pts = [pvx1.origin, pvx1.target, pvx2.target, pvx2.origin]
				ptinter = G6.intersect_line_polygon(ray, pts)
				return true if ptinter && eye.distance(ptinter) <= d0
			end	
		end	
	end
	
	#No occulting
	false
end

#--------------------------------------------------
# ALGO: Manage the computation flow
#--------------------------------------------------

#ALGO: Analyze the grouping from face pickers
def algo_analyze_groupings
	grouping0 = nil
	@initial_face_id = @initial_face.entityID
	@lst_groupings = []
	@hsh_all_tr = {}
	@hsh_master_groupings = {}
	@hsh_master_cdef = {}
	
	#Putting the grouping containing the initial face in first position
	ls_picked_grouping = []
	@picked_groupings.each do |picked_grouping|
		hfaces, tr, parent = picked_grouping
		if parent == @parent && hfaces[@initial_face_id]
			ls_picked_grouping.unshift picked_grouping
		else
			ls_picked_grouping.push picked_grouping
		end
	end
	
	#Creating the Grouping structures and detecting multiple component instances
	ls_picked_grouping.each do |picked_grouping|
		hfaces, tr, parent = picked_grouping
		
		#Skip if the parent is a component instance already found
		if parent.instance_of?(Sketchup::ComponentInstance)
			cdef = parent.definition
			#cdef_id = cdef.entityID
			cdef_id = G6.entityID(cdef)
			next if @hsh_master_groupings[cdef_id]
			@hsh_master_groupings[cdef_id] = cdef_id
		end
		
		#Creating the structure
		grouping = Grouping.new
		grouping.parent = parent
		grouping.tr = tr
		grouping.hfaces = hfaces
		grouping.lst_blocks = []
		@lst_groupings.push grouping		
	end	
	
	#Populating to other instances of components
	preview_on_component = @param_preview_on_component
	if preview_on_component
		htr = {}
		hgrouping = {}
		@lst_groupings.each do |grouping|
			parent = grouping.parent
			next unless parent.instance_of?(Sketchup::ComponentInstance)
			cdef_id = parent.definition.entityID
			htr[cdef_id] = []
			hgrouping[cdef_id] = grouping
		end
		
		if hgrouping.length > 0
			component_instances_transformations(htr)
			hgrouping.each do |cdef_id, grouping|
				grouping.tr_slaves = htr[cdef_id]
			end
		end
	end
end

#ALGO: Find top-level transformations for component instances given by their definition id
#To be called only with first argument.
#Complement the value of the hash array as a list of transformation for each def id
def component_instances_transformations(htr, entities=nil, t=nil)
	unless entities
		entities = Sketchup.active_model.active_entities
		t = Geom::Transformation.new
	end	
	
	lscomp = entities.grep(Sketchup::ComponentInstance) + entities.grep(Sketchup::Group)
	lscomp.each do |comp|
		next unless comp.instance_of?(Sketchup::ComponentInstance)
		cdef_id = comp.definition.entityID
		tnext = t * comp.transformation
		if htr.has_key?(cdef_id)
			ltr = htr[cdef_id]
			ltr = htr[cdef_id] = [] unless ltr
			ltr.push tnext
		elsif comp.instance_of?(Sketchup::Group)
			component_instances_transformations htr, comp.entities, tnext
		elsif comp.instance_of?(Sketchup::ComponentInstance)
			component_instances_transformations htr, comp.definition.entities, tnext
		end	
	end	
end

#ALGO: Rewind all the context to restart from a new selection
def algo_rewind
	@lst_blocks = []
	@hsh_pseudofaces = {}
	@hsh_pseudoedges = {}
	@hsh_pseudovx = {}
	@prepare_vertices_done = false
	@vector_lock = @last_vector_cur if @jpp_mode == :vector
end

#ALGO: Prepare calculation - top method with protection
def algo_prepare_calculation
	return false unless @initial_face
	begin
		algo_prepare_calculation_exec
		return true
	rescue Exception => e
		Traductor::RubyErrorDialog.invoke e, @title, T6[:ERR_PrepareCalculation]
		abort_tool
	end
	false
end

#ALGO: Prepare calculation - real method
def algo_prepare_calculation_exec
	#Analyze the groupings
	algo_rewind
	algo_analyze_groupings
	
	#Dispatch in blocks of adjacent faces
	#algo_rewind

	#Split the groupings into contiguous blocks
	@block_cur = nil
	@lst_groupings.each { |grouping| block_splitting grouping }
	
	#Compute the vertices for each block
	block_prepare_vertices_all
	
	#Assigning random factors if required
	@random_assigned = false
	random_assign
	
	#Counting the number of faces
	@nb_total_faces = 0
	@lst_blocks.each { |block| @nb_total_faces += block.lst_pfaces.length }
	
	#Computing the shield faces
	shield_select_candidates
end

#ALGO: Handle double click for repeat push pull
def algo_repeat_pushpull
	case @mode
	when :selection, :dragging
		if (@mode != :selection || @initial_face_cur) && @offset && @offset != 0
			@vector_cur = @last_vector_cur
			algo_rewind
			modify_preparation
			return if @in_error
			block_compute_all
			geometry_execute
		else
			exit_tool
		end
	end	
end

#ALGO: Notification that the offset has changed direction
def algo_notify_offset_direction
	
end

#--------------------------------------------------
# BLOCK: Manage the computation for blocks
#--------------------------------------------------

#BLOCK: Split a block into joint faces
def block_splitting(grouping)
	#Grouping is another instance. Create the blocks as slaves
	parent = grouping.parent
	tr = grouping.tr
	hfaces = grouping.hfaces
	
	#Calculating the lead face
	face_id0 = @initial_face.entityID
	if hfaces[face_id0]
		face0 = @initial_face
		reversed0 = @normal_ini % G6.transform_vector(@initial_face.normal, @tr) < 0
	else
		face0 = nil
		hfaces.each { |id, f| face0 = f ; break }
		reversed0 = false
	end
	return unless face0
	
	#Splitting the face grouping into blocks
	hsh_pvx = {}
	hpfaces_in_list = {}
	hpfaces_treated = {}
	lst_pfaces_ordered = []
	lst_pfaces = [pseudoface_create(face0, reversed0)]
	lst_pholes = []
	while lst_pfaces.length > 0
		pface = lst_pfaces.shift
		face = pface.face
		face_id = face.entityID
		hpfaces_in_list[face_id] = hpfaces_treated[face_id] = pface
		lst_pfaces_ordered.push pface
		
		#Creating the pseudo-vertices for the face
		face.loops.each do |loop|
			loop.vertices.each do |vx|
				vx_id = vx.entityID
				pvx_id = @proc_key_pseudo.call(vx_id, face_id)
				unless hsh_pvx[pvx_id]
					pvx = hsh_pvx[pvx_id] = pseudovertex_create(vx, pvx_id, pface, tr)
				end	
			end
		end
		
		#Computing the next adjacent faces. Handling the orientation of faces
		coquad_possible = (face.vertices.length == 3)
		face.edges.each do |edge|
			erev_main = edge.reversed_in?(face)
			edge.faces.each do |f|
				next if f == face
				f_id = f.entityID
				next if !hfaces[f_id] || hpfaces_in_list[f_id]
				reversed = pface.reversed
				reversed = !reversed if (erev_main == edge.reversed_in?(f))	#faces have different orientation
				pf = pseudoface_create(f, reversed)
				hpfaces_in_list[f_id] = f
				lst_pfaces.push pf
				
				#Detecting coquads
				if coquad_possible && f.vertices.length == 3 && !edge.casts_shadows? && edge.smooth? && edge.soft?
					pf.coquad = pface
					pface.coquad = pf
				end
			end
		end	
		
		#Handling holes if any
		face.loops.each_with_index do |loop, iloop|
			next if loop.outer?
			phole = pseudohole_create(pface, iloop)
			lst_pholes.push phole
		end
		
		next unless lst_pfaces.empty?
		
		#Creating the block
		block = Block.new
		grouping.lst_blocks = [] unless grouping.lst_blocks
		grouping.lst_blocks.push block
		@lst_blocks.push block
		block.hpfaces = hpfaces_in_list
		block.lst_pfaces = lst_pfaces_ordered
		block.grouping = grouping
		block.tr = tr
		block.tr_inv = tr.inverse
		block.parent = parent
		block.hsh_pvx = hsh_pvx
		block.lst_pholes = lst_pholes
		@block_cur = block if hpfaces_in_list[face_id0]
		
		#Calculating the Borders
		block_compute_borders block, hfaces
		
		#Determining if block is single face and need forcing thickening (to mimic SU PushPull)
		if block.lst_pfaces.length == 1
			upface = block.lst_pfaces.first
			uface = upface.face
			unless uface.outer_loop.edges.find { |e| e.faces.length > 1 }
				block.force_thicken = true
			end	
		end
			
		#Reinitialization for analyzing next blocks
		lst_pfaces = []
		lst_pfaces_ordered = []
		hpfaces_in_list = {}
		hsh_pvx = {}
		lst_pholes = []
		hfaces.each do |id, f| 
			unless hpfaces_treated[f.entityID]
				lst_pfaces = [pseudoface_create(f, reversed0)]
				break
			end	
		end
		
	end	
end

#BLOCK: Calculate the bordering vertices for a block (jointive push-pull)
def block_compute_borders(block, hfaces)
	#Detecting the borders (edge with only one face of the block)
	hsh_pvx = block.hsh_pvx
	hsh_ped = block.hsh_ped = {}
	lst_peds = block.lst_peds = []
	hpfaces = block.hpfaces
	hprotected = block.hsh_edge_protected = {}
	block.lst_pfaces.each do |pface|
		face = pface.face
		face_id = face.entityID
		face.edges.each do |edge|
			edge_id = @proc_key_pseudo.call edge.entityID, face_id
			next if hsh_ped[edge_id]
			ped = hsh_ped[edge_id] = pseudoedge_create(edge, edge_id, pface, hsh_pvx)
			lst_peds.push ped
			hprotected[edge.entityID] = [edge.start.position, edge.end.position] if edge.faces.length == 1
			lf = edge.faces.find_all { |f| hfaces[f.entityID] }		
			if lf.length == 1
				[ped.pvx1_id, ped.pvx2_id].each { |id| hsh_pvx[id].at_border = true }
				ped.at_border = true
			else
				ped.dashed = (ped.soft || ped.smooth || ped.hidden)
			end
		end	
	end	
	
	#Calculating if border edge at vertex is shown as dashed
	hsh_pvx.each do |pvx_id, pvx|
		vx = pvx.vx
		curve = vx.curve_interior?
		if curve
			pvx.dashed = vertex_dashed?(pvx, curve)
		end
	end
end

#BLOCK: Check if a vertices is dashed (soft) by default
def vertex_dashed?(pvx, curve)
	return true if @jpp_prop_jointive
	lped_curve = pvx.lpeds.find_all { |ped| ped.edge.curve == curve }
	return true if lped_curve.length > 1
	return false if lped_curve.length == 0	
	
	false
end

#BLOCK: Compute the block for a given Height
def block_compute_all(shield=false)
	@random_on = @jpp_prop_random && option_get(:random_on)
	
	#Special calculation for Vector Push Pull
	if @jpp_mode == :vector 
		offset0 = @offset
		flat = option_get :vector_projected
		vec0 = (@vector_cur) ? @vector_cur : X_AXIS
		vec0 = @planar_dir if @planar_dir
		vec0 = vec0.reverse if @vector_cur && vec0 % @vector_cur < 0
		@lst_blocks.each do |block|
			vec = (@dirman.get_scope_local && @planar_dir) ? block.tr * vec0 : vec0
			hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
			
			#Projected mode
			if flat
				centroid = block_compute_centroid block
				centroid_target = centroid.offset(vec, offset0)
				pt = (@mode == :dragging && block == @block_cur) ? @target_cur : centroid_target
				plane = [pt, vec]
				hpvx.each do |id, pvx|
					offset = (@random_on) ? offset0 * pvx.pface.rand_factor : offset0
					target = G6.robust_offset pvx.origin, vec, offset
					line = [pvx.origin, target]
					pvx.target = Geom.intersect_line_plane line, plane
				end	
				
			#Normal Vector Mode	
			else	
				hpvx.each do |id, pvx|
					offset = (@random_on) ? offset0 * pvx.pface.rand_factor : offset0
					pvx.target = G6.robust_offset pvx.origin, vec, offset
				end	
			end	
		end	

	#Special calculation for extrude Push Pull
	elsif @jpp_mode == :extrude
		offset0 = @offset
		flat = option_get :extrude_flat
		if flat
			@lst_blocks.each do |block|
				vec = block.vector_extrude
				vec = vector_adjust_normal vec, block.planar_dir
				block.centroid_target = block.centroid.offset(vec, offset0)
				pt = (@mode == :dragging && block == @block_cur) ? @target_cur : block.centroid_target
				plane = [pt, vec]
				hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
				hpvx.each do |id, pvx|
					offset = (@random_on) ? offset0 * pvx.pface.rand_factor : offset0
					line = [pvx.origin, pvx.origin.offset(vec, offset)]
					pvx.target = Geom.intersect_line_plane line, plane
				end	
			end	
		else
			@lst_blocks.each do |block|
				vec = block.vector_extrude
				vec = vector_adjust_normal vec, block.planar_dir
				block.centroid_target = block.centroid.offset(vec, offset0)
				hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
				hpvx.each do |id, pvx|
					offset = (@random_on) ? offset0 * pvx.pface.rand_factor : offset0
					pvx.target = pvx.origin.offset vec, offset
				end	
			end	
		end

	#Round mode	
	elsif @jpp_mode == :round
		ipos = (@offset >= 0) ? 0 : 1
		@lst_blocks.each do |block|
			hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
			hpvx.each do |id, pvx|
				if pvx.vec[ipos]
					offset = @offset * pvx.factor[ipos]
					pvx.target = G6.robust_offset pvx.origin, pvx.vec[ipos], offset
				else
					pvx.target = pvx.origin
				end	
			end	
		end	
		
	#Other modes	
	else
		@lst_blocks.each do |block|
			hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
			hpvx.each do |id, pvx|
				offset = @offset * pvx.factor
				offset *= pvx.pface.rand_factor if @random_on
				puts "pvx.vec pa valide = #{pvx.vec}" unless pvx.vec.valid?
				puts "pvx.vec = #{pvx.vec}" if pvx.vec.valid? && pvx.vec.length < 0.1
				pvx.target = G6.robust_offset pvx.origin, pvx.vec, offset
			end	
		end	
	end	
	
	#Radial scaling when applicable
	block_radial_scaling shield if @jpp_prop_radial_scaling
	
	#Update the wireframe
	wireframe_compute unless shield
end

#BLOCK: Compute the centroid of a block (vector mode)
def block_compute_centroid(block)
	return block.centroid if block.centroid

	tr = block.tr
	x = y = z = 0
	sumarea = 0
	block.hpfaces.each do |id, pface|
		face = pface.face
		centroid, area = G6.face_centroid_area(pface.face)
		x += centroid.x * area
		y += centroid.y * area
		z += centroid.z * area
		sumarea += area
	end
	block.centroid = tr * Geom::Point3d.new(x / sumarea, y / sumarea, z / sumarea)	
end

#BLOCK: Compute the radial scaling (Normal and Extrude modes only)
def block_radial_scaling(shield)
	fac = option_get :radial_scaling
	return unless fac && fac != 1.0

	case @jpp_mode
	
	#Extrude Mode
	when :extrude
		fac = option_get :radial_scaling
		return unless fac && fac != 1.0
		fac1 = 1 - fac
		@lst_blocks.each do |block|
			centroid_target = block.centroid_target
			hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
			hpvx.each do |id, pvx|
				pvx.target = Geom.linear_combination fac, pvx.target, fac1, centroid_target
			end	
		end	
	
	when :normal
		fac1 = 1 - fac
		@lst_blocks.each do |block|
			hpvx = (shield) ? block.shield_hpvx : block.hsh_pvx
			hpvx.each do |id, pvx|
				d = pvx.origin.distance pvx.target
				target_ref = G6.robust_offset pvx.ref_radial, pvx.vec, d
				pvx.target = Geom.linear_combination fac, pvx.target, fac1, target_ref
			end	
		end	
	
	end
end

#BLOCK: Top Calculation method of vertex directions
def block_prepare_vertices_all
	return unless @lst_blocks && !@prepare_vertices_done
	
	#Computing the planar direction if applicable
	@lst_blocks.each do |block| 
		block.planar_dir = (@planar_local && @planar_dir) ? G6.transform_vector(@planar_dir, block.tr) : @planar_dir
	end	

	case @jpp_mode
	when :vector
		#nothing to do
		
	#JOINT: Directions calculated depending on angle
	when :joint
		@lst_blocks.each { |block| block_prepare_vertices_J block }
		
	#ROUND: Directions calculated depending on angle
	when :round
		@lst_blocks.each { |block| block_prepare_vertices_R block }
		
	#EXTRUDE: direction is based on average directions of faces in block	
	when :extrude
		@lst_blocks.each { |block| block_prepare_vertices_X block }

	#EXTRUDE: direction is based on average directions of faces in block	
	when :follow
		@lst_blocks.each { |block| block_prepare_vertices_F block }
		
	#NORMAL: Direction is just the normal to the face	
	when :normal
		@lst_blocks.each { |block| block_prepare_vertices_N block }
	end	
	
	#Computing the cofaces (coplanar to directions at borders)
	@lst_blocks.each { |block| coface_compute block } unless @jpp_mode == :vector 
	
	#Recording the preparation is done
	@prepare_vertices_done = true
end

#-------------------------------------------------------------
# NORMAL: Computation at Vertex for NORMAL mode
#-------------------------------------------------------------

#NORMAL: Calculation for NORMAL Push Pull
def block_prepare_vertices_N(block)
	tr = block.tr
	option_coquad = option_get :coquad
	planar_dir = block.planar_dir
	
	#Calculation with preservation of coquad integrity
	if option_coquad
		block.hsh_pvx.each do |id, pvx|
			pface = pvx.pface
			pf = pface.coquad
			if pf
				normal = G6.vector_straight_average([pface.normal, pf.normal])
				pvx.factor = vertex_average_factor normal, [[pface.face, pface.normal], [pf.face, pf.normal]]
			else
				normal = pface.normal
				pvx.factor = 1.0				
			end	
			normal = G6.transform_vector(normal, tr)	
			pvx.vec = vector_adjust_normal(normal, block.planar_dir)	
		end
		
	#No coquad integrity	
	else	
		block.hsh_pvx.each do |id, pvx|
			normal = G6.transform_vector(pvx.pface.normal, tr)	
			pvx.vec = vector_adjust_normal(normal, block.planar_dir)
			pvx.factor = 1.0				
		end
	
	end
	
	#Computing the reference center for radial scaling
	block.hsh_pvx.each do |id, pvx|
		pvx.ref_radial = tr * pvx.pface.face.bounds.center
	end

end

#-------------------------------------------------------------
# EXTRUDE: Computation at Vertex for EXTRUDE mode
#-------------------------------------------------------------

#EXTRUDE: Calculation for VECTOR Push Pull
def block_prepare_vertices_X(block)
	tr = block.tr
	planar_dir = block.planar_dir
	x = y = z = 0
	vx = vy = vz = 0
	sumarea = 0
	block.hpfaces.each do |id, pface|
		face = pface.face
		centroid, area = G6.face_centroid_area(pface.face)
		normal = (pface.reversed) ? face.normal.reverse : face.normal
		normal = G6.transform_vector(normal, tr)	
		normal = vector_adjust_normal(normal, planar_dir)	
		x += centroid.x * area
		y += centroid.y * area
		z += centroid.z * area
		vx += normal.x * area
		vy += normal.y * area
		vz += normal.z * area
		sumarea += area
	end
	block.centroid = tr * Geom::Point3d.new(x / sumarea, y / sumarea, z / sumarea)
	vector_extrude = block.vector_extrude = Geom::Vector3d.new(vx, vy, vz)
	
	unless vector_extrude.valid?
		UI.beep
		@in_error = true
		set_state_mode :selection
		return
	end
	
	block.hsh_pvx.each { |vx_id, pvx| pvx.vec = vector_extrude }
end

#-------------------------------------------------------------
# JOINT: Computation at Vertex for JOINT mode
#-------------------------------------------------------------

#JOINT: Calculation for JOINT and ROUND Push Pull
def block_prepare_vertices_J(block)
	block.hsh_pvx.each { |id, pvx| joint_compute_direction pvx, block }
end

#JOINT: compute the average direction at vertex for JOINT mode
def joint_compute_direction(pvx, block)
	#Block parameters
	planar_dir = block.planar_dir
	tr = block.tr
	hpfaces = block.hpfaces
	influence = option_get(:neighbour_influence)

	#Faces at vertex	
	faces = pvx.vx.faces
	
	#Contribution of the top face in selection
	normal0 = nil
	ls_info = []
	lst_faces_in = faces.find_all { |face| hpfaces[face.entityID] }
	lst_faces_in.each do |face|
		normal = hpfaces[face.entityID].normal
		ls_info.push [face, normal]
		normal0 = normal
	end
	
	#Taking into account adjacent faces if influence is requested
	if influence
		lst_faces_out = faces.find_all { |face| !hpfaces[face.entityID] }
		lst_faces_out.each do |face|
			normal = face.normal
			normal = normal.reverse if normal0 % normal <= 0
			ls_info.push [face, normal]
		end
	end
	
	#Contributing vectors and faces
	contributing_faces = []
	lsg_vec = []
	ls_info.each do |face, normal|
		normal = G6.transform_vector(normal, tr)
		normal = vector_adjust_normal normal, planar_dir
		next unless normal.valid?
		lsg_vec.push normal.normalize
		contributing_faces.push [face, normal]		
	end	
	
	#Averaging the vectors by group	
	n = lsg_vec.length
	lsg_vec = G6.vector_grouping(lsg_vec)
		
	#Calculating the resulting direction and factor
	if lsg_vec.length == 3 && n > 6
		vec_res = G6.vector_straight_average lsg_vec
	else
		vec_res = G6.vector_exact_average lsg_vec
	end	
	pvx.vec = vec_res
	pvx.factor = vertex_average_factor vec_res, contributing_faces
end

#-------------------------------------------------------------
# FOLLOW: Computation at Vertex for FOLLOW mode
#-------------------------------------------------------------

#FOLLOW: Calculation for JOINT and ROUND Push Pull
def block_prepare_vertices_F(block)
	block.hsh_pvx.each { |id, pvx| follow_compute_direction pvx, block }
end

#FOLLOW: compute the average direction at vertex for FOLLOW mode
def follow_compute_direction(pvx, block)
	#Block parameters
	planar_dir = block.planar_dir
	tr = block.tr
	hpfaces = block.hpfaces
	
	#Normals at vertex	
	contributing_faces = []
	lsg_vec = []
	pvx.vx.faces.each do |face|
		pface = hpfaces[face.entityID]
		next unless pface
		normal = G6.transform_vector pface.normal, tr
		normal = vector_adjust_normal normal, planar_dir
		next unless normal.valid?
		lsg_vec.push normal.normalize
		contributing_faces.push [face, normal]	
	end	
	
	#Check if vertex on border and can give direction
	vec = follow_vertex_master_vector(pvx, hpfaces)
	if vec
		vec_res = vector_adjust_normal G6.transform_vector(vec, tr), planar_dir
	else
		vec_res = G6.vector_nice_average(lsg_vec)	
	end
	
	#Calculating the resulting direction and factor
	pvx.vec = vec_res
	pvx.factor = vertex_average_factor vec_res, contributing_faces
end

#FOLLOW: Dtermine the master vector at border if any
def follow_vertex_master_vector(pvx, hpfaces)
	return nil unless pvx.at_border
	vx = pvx.vx
	pface = pvx.pface
	normal0 = pface.face.normal.normalize
	ledges = []
	
	#Finding edges which are not used by the face
	vx.edges.each do |edge|
		next if edge.faces.length > 0 && edge.faces.find { |f| hpfaces[f.entityID] }
		vec_edge = edge.start.position.vector_to(edge.end.position).normalize
		ps = (normal0 % vec_edge).abs
		ledges.push [edge, ps] #if ps > 0.4
	end	
	return nil if ledges.empty?
	
	ledges = ledges.sort { |a, b| b[1] <=> a[1] }
	ledges[0][0].other_vertex(vx).position.vector_to(vx.position)
end

#-------------------------------------------------------------
# ROUND: Computation at Vertex for ROUND mode
#-------------------------------------------------------------

#ROUND: Calculation for JOINT and ROUND Push Pull
def block_prepare_vertices_R(block)
	#Compute the directions
	block.hsh_pvx.each { |id, pvx| pvx.vec = [] ; pvx.factor = [1.0, 1.0] }
	block.hsh_pvx.each { |id, pvx| round_compute_direction(pvx, block) unless (pvx.vec.find_all {|u| u }).length == 2 }
	
	#Compute the junctions
	junction_explore_from_edge block
end

#ROUND: Process the exploration at a vertex and calculate directions and junctions
def round_compute_direction(pvx, block)
	#Block parameters
	planar_dir = block.planar_dir
	tr = block.tr
	hpvx = block.hsh_pvx
	hpfaces = block.hpfaces
	angle_max = @angle_round.degrees

	#Ordered faces at vertex
	vx = pvx.vx
	vx_id = vx.entityID
	face = pvx.pface.face
	origin = tr * vx.position
	
	#Contributing faces at vertex
	faces = vx.faces.find_all { |f| hpfaces[f.entityID] }
	pfaces = faces.collect { |f| hpfaces[f.entityID] }
	normals = pfaces.collect { |pface| vector_adjust_normal(G6.transform_vector(pface.normal, tr), planar_dir).normalize }
	nf = faces.length - 1
	
	#Only one face contributing
	if nf == 0
		pvx.vec = [normals[0], normals[0]]
		pvx.factor = [1.0, 1.0]
		return
	end	
	
	#Grouping faces which have close normals
	groups = []
	lf_face_groups = []
	for i in 0..nf
		for j in i+1..nf
			next if (faces[i].edges & faces[j].edges).empty? || !normals[i].valid? || !normals[j].valid? 
			next if normals[i].angle_between(normals[j]) > angle_max
			round_in_same_group(i, j, lf_face_groups, groups)
		end	
	end
	groups = groups.compact
	
	#Complementing with standalone groups
	for i in 0..nf
		groups.push [i] unless lf_face_groups[i]
	end	
	
	#Computing the normals for close groups
	group_normals = []
	groups.each do |grp|
		lvec = grp.collect { |i| normals[i] }
		group_normals.push G6.vector_nice_average(lvec).normalize
	end
	
	#Determining convergence / divergence between groups
	ng = groups.length - 1
	supp_conv = []
	supp_div = []
	for i in 0..ng
		lg_conv = []
		lg_div = []
		grp = groups[i]
		face = faces[grp[0]]
		normal = group_normals[i]
		for j in 0..ng
			next if i == j
			if round_faces_convergence?(face, normal, faces[groups[j][0]], group_normals[j], tr)
				lg_conv.push j
				supp_conv.push [faces[groups[j][0]], group_normals[j]]
			else
				lg_div.push j
				supp_div.push [faces[groups[j][0]], group_normals[j]]
			end	
		end	
		
		#Positive offset
		for ipos in 0..1
			if ipos == 0
				lvec_conv = ([i] + lg_conv).collect { |k| group_normals[k] }
				lvec_div = lg_div.collect { |k| group_normals[k] }
				supp = supp_conv
			else
				lvec_conv = ([i] + lg_div).collect { |k| group_normals[k] }
				lvec_div = lg_conv.collect { |k| group_normals[k] }
				supp = supp_div
			end
			
			#Calculating the resulting vector
			vec_conv = G6.vector_exact_average(lvec_conv).normalize
			vec_div = (lvec_div.empty?) ? nil : G6.vector_exact_average(lvec_div).normalize
			#vec_conv = G6.vector_exact_average([vec_conv.normalize, vec_div.normalize]).normalize if vec_div
			#vec_conv = vector_adjust_normal(vec_conv, vec_div).normalize if vec_div #&& lvec_conv.length > 1
			
			#Calculating the contributing faces and factor
			contributing_faces = grp.collect { |iface| [faces[iface], normals[iface]] }
			#contributing_faces += supp
			factor = vertex_average_factor vec_conv, contributing_faces
			#puts "           Vec POS=#{ipos} ===> vec_conv = #{vec_conv} vec_div = #{vec_div} factor = #{factor}"

			#Assigning to contributing faces
			contributing_faces.each do |face, normal|
				vid = @proc_key_pseudo.call(vx_id, face.entityID)
				ppvx = hpvx[vid]
				puts "+++++++++++++++++ERROR VID face = #{face.entityID} vid = #{vid}" unless ppvx
				puts "+++++++++++++++++ERROR FACTOR" unless factor
				next unless ppvx
				ppvx.vec[ipos] = vec_conv
				ppvx.factor[ipos] = factor
			end	
		end
	end
end

#ROUND: Utility method to Register that 2 faces are close and to update the groups accordingly
def round_in_same_group(i, j, lf_groups, groups)
	grp_i = lf_groups[i]
	grp_j = lf_groups[j]
	if grp_i && grp_j
		grp = groups[grp_i] | groups[grp_j]
		groups[grp_i] = groups[grp_j] = nil
		groups.push grp
		n = groups.length - 1
		grp.each { |k| lf_groups[k] = n }
	elsif grp_i
		groups[grp_i].push j
		lf_groups[j] = grp_i
	elsif grp_j
		groups[grp_j].push i
		lf_groups[i] = grp_j
	else
		groups.push [i, j]
		lf_groups[i] = lf_groups[j] = groups.length - 1
	end	
end

#ROUND: Utility method to check the convergence of two faces with normals
def round_faces_convergence?(face1, normal1, face2, normal2, tr)
	pt1 = tr * face1.bounds.center
	pt2 = tr * face2.bounds.center
	ofpt1 = G6.robust_offset pt1, normal1, 10
	ofpt2 = G6.robust_offset pt2, normal2, 10
	(ofpt1.distance(ofpt2) < pt1.distance(pt2))
end

#-------------------------------------------------------------
# COFACE: Computation of Coplanar faces to directions
#-------------------------------------------------------------

#COFACE: Create a coface for a pseudo edge
def coface_create(ped, face)
	coface = Coface.new
	coface.face = face
	coface.face_id = face.entityID
	ped.matinfo = [face.material, face.back_material]
	coface
end

#COFACE: Compute the coface for each edge
def coface_compute(block)
	hped = block.hsh_ped
	hpvx = block.hsh_pvx
	hpfaces = block.hpfaces
	tr = block.tr
	
	hped.each do |ped_id, ped|
		ped.coface = nil
		edge = ped.edge
		faces = edge.faces.find_all { |f| !hpfaces[f.entityID] }
		next if faces.length != 1
		border_face = faces[0]
		normal = G6.transform_vector(border_face.normal, tr)
		pvx1 = hpvx[ped.pvx1_id]
		pvx2 = hpvx[ped.pvx2_id]
		vx1 = pvx1.vx
		vx2 = pvx1.vx
		vec1 = (pvx1.vec.class == Array) ? pvx1.vec.first : pvx1.vec
		vec2 = (pvx2.vec.class == Array) ? pvx2.vec.first : pvx2.vec
		next unless vec1 && vec2
		if (vec1 % normal).abs < 0.001 && (vec2 % normal).abs < 0.001
			coface = ped.coface = coface_create(ped, border_face)
			pvx1.cofacing = pvx2.cofacing = true
		end	
	end
end

#-------------------------------------------------------------
# VERTEX: Computation at Vertex for ROUND and JOINT modes
#-------------------------------------------------------------

#VERTEX: Computing the factor to respect offset based on contributing faces
def vertex_average_factor(vec0, faces_info)
	return 0.0 unless vec0 && vec0.valid?
	face0 = normal0 = nil
	area0 = 0
	area0h = 0
	
	#Privileging the face with largest area
	faces_info.each do |info|
		face, normal = info
		area = face.area
		if face.loops.length > 1
			if area > area0h
				area0h = area
				face0 = face
				normal0 = normal
			end
		elsif area0h == 0 && area > area0
			area0 = area
			face0 = face
			normal0 = normal
		end
	end	
	
	#Computing the factor as 1.0 / cosinus
	cosinus = Math::cos normal0.angle_between(vec0)
	(cosinus.abs < 0.05) ? 1.0 : 1.0 / cosinus	
end

#-------------------------------------------------------------
# VECTOR: Utilities for Vectors computation
#-------------------------------------------------------------

#VECTOR: Compute the projection of a vector along a planar direction defined by its normal
def vector_after_projection(vec, normal)
	return vec unless normal
	return normal if @jpp_prop_vector
	pt = ORIGIN.offset(vec, 10).project_to_plane([ORIGIN, normal])
	ORIGIN.vector_to pt
end

def vector_adjust_normal(vec, normal)
	return vec unless normal
	return normal if @jpp_prop_vector
	####return vec if (vec % normal).abs > 0.98
	pt = ORIGIN.offset(vec, 10).project_to_plane([ORIGIN, normal])
	ORIGIN.vector_to pt
end

#-------------------------------------------------------------
# JUNCTION: Blend surfaces joining edges and corners
#-------------------------------------------------------------

#JUNCTION: Explore the junction from edges in a block
def junction_explore_from_edge(block)
	hsh_edges_used = {}
	hsh_junctions_vx = block.hsh_junctions_vx = [{}, {}]
	lst_junctions = block.lst_junctions = [[], []]
	hsh_roundings = block.hsh_roundings = [{}, {}]
	
	hpvx = block.hsh_pvx
	hped = block.hsh_ped
	hpfaces = block.hpfaces
	
	hped.each do |ped_id, ped|
		pvx1 = hpvx[ped.pvx1_id]
		pvx2 = hpvx[ped.pvx2_id]
		vx1 = pvx1.vx
		vx2 = pvx2.vx
		edge = vx1.common_edge vx2
		edge_id = edge.entityID
		next if hsh_edges_used[edge_id]
		hsh_edges_used[edge_id] = edge
		faces = edge.faces
		next if faces.length < 2
		f1, f2 = edge.faces.find_all { |f| hpfaces[f.entityID] }
		next unless f2 && f1
		if vx1 == edge.end
			a = pvx1
			pvx1 = pvx2
			pvx2 = a
		end	
		led_junction1 = junction_create_at_vertex(hpvx, edge.start, f1, f2, ped, 0, lst_junctions, hsh_junctions_vx)
		led_junction2 = junction_create_at_vertex(hpvx, edge.end, f1, f2, ped, 1, lst_junctions, hsh_junctions_vx)
		rounding_create_at_edge(edge_id, edge, led_junction1, led_junction2, f1, f2, pvx1, pvx2, hpvx, hsh_roundings)
	end
end

#ROUNDING: Create a rounding from 1 or 2 junctions
def rounding_create_at_edge(edge_id, edge, led_junction1, led_junction2, face1, face2, pvx1, pvx2, hpvx, hsh_roundings)
	return if led_junction1.empty? && led_junction2.empty?
	
	reverse = edge.reversed_in?(face1)
	
	for ipos in 0..1
		junction1 = led_junction1[ipos]
		junction2 = led_junction2[ipos]
		next unless junction1 || junction2
		rounding = Rounding.new
		rounding.edge_id = edge_id
		rounding.face1_id = face1.entityID
		rounding.face2_id = face2.entityID
		rounding.junction1 = junction1
		rounding.junction2 = junction2
		rounding.pvx1 = pvx1
		rounding.pvx2 = pvx2
		rounding.vector = pvx1.origin.vector_to pvx2.origin
		rounding.reverse = (ipos == 0) ? reverse : !reverse	####
		junction1.rounding = rounding if junction1
		junction2.rounding = rounding if junction2
		hsh_roundings[ipos][edge_id] = rounding
	end
end

#JUNCTION: Check for the junction at a vertex between 2 faces and create it if applicable
def junction_create_at_vertex(hpvx, vx, face1, face2, ped, iped, lst_junctions, hsh_junctions_vx)
	vx_id = vx.entityID
	vx1_id = @proc_key_pseudo.call vx_id, face1.entityID
	vx2_id = @proc_key_pseudo.call vx_id, face2.entityID
	pvx1 = hpvx[vx1_id]
	pvx2 = hpvx[vx2_id]
	
	puts "PVX mauvais #{vx1_id} 1" unless pvx1.vec
	puts "PVX mauvais #{vx1_id} 2" unless pvx2.vec
	
	led_junctions = []
	for ipos in 0..1
		vec1 = pvx1.vec[ipos]
		vec2 = pvx2.vec[ipos]
		next if vec1 == vec2 || !vec1 || !vec2 || !vec1.valid? || !vec2.valid?
		junction = Junction.new
		junction.ipos = ipos
		junction.pvx1 = pvx1
		junction.pvx2 = pvx2
		junction.vec1 = vec1
		junction.vec2 = vec2
		junction.face1_id = face1.entityID
		junction.face2_id = face2.entityID
		junction.ped = ped
		junction.iped = iped
		junction.angle, junction.origin, junction.lst_vec = junction_interpolate_vectors(ipos, pvx1, pvx2, 5.degrees)
		led_junctions[ipos] = junction
		lst_junctions[ipos].push junction
		
		#Assigning the junction to the pseudo edge
		if iped == 0 
			ljped = ped.junction1
			ljped = ped.junction1 = [] unless ljped
		else
			ljped = ped.junction2
			ljped = ped.junction2 = [] unless ljped
		end
		ljped[ipos] = junction
		pvx1.junction = pvx2.junction = junction
		
		hhj = hsh_junctions_vx[ipos]
		lj = hhj[vx_id]
		lj = hhj[vx_id] = [] unless lj
		lj.push junction
	end	
	led_junctions
end

#JUNCTION: Calculate the points for a junction (preview and geometry mode)
def junction_calculate_points(junction, nb_seg=nil)
	if @offset < 0
		ipos = 1
	else
		ipos = 0
	end
	pvx1 = junction.pvx2
	pvx2 = junction.pvx1
	vec1 = pvx1.vec[ipos]
	vec2 = pvx2.vec[ipos]
	normal = vec1 * vec2
	factor1 = pvx1.factor[ipos]
	factor2 = pvx2.factor[ipos]
	target1 = pvx1.target
	target2 = pvx2.target
	
	#Calculating the optimal number of segments if not specified
	unless nb_seg
		anglemax = 15.degrees
		angle = vec1.angle_between vec2
		nb_seg = (angle / anglemax).round
	end	
	return [target1, target2] if nb_seg < 2 || !vec1.valid? || !vec2.valid? ||!normal.valid?
	
	#Calculating the profile
	junction_compute_profile(nb_seg, target1, vec1 * normal, target2, vec2 * normal).reverse
end

#ROUND: Compute the profile of the junction
def junction_compute_profile(nb_seg, pt1, vec1, pt2, vec2)
	#Computing the origin and offsets
	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
	normal = vec1 * vec2
	return [pt1, pt2] unless vec1.valid? && vec2.valid? && normal.valid?

	#Getting the nominal circular profile - Caching them for performance
	pts = @golden_circular_pts[nb_seg]
	unless pts
		pts = @golden_circular_pts[nb_seg] = []
		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	
	end
	
	#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
	taxe = Geom::Transformation.axes origin, vec1, normal * vec1, normal
	t = taxe * tsh * ts * tsg
	
	#Performing the transformation
	pts.collect { |pt| t * pt }
end

#JUNCTION: Interpolate the vectors of directions 
def junction_interpolate_vectors(ipos, pvx1, pvx2, anglemax)
	vec1 = pvx1.vec[ipos]
	vec2 = pvx2.vec[ipos]
	factor1 = pvx1.factor[ipos]
	factor2 = pvx2.factor[ipos]
	origin = pvx1.origin
	target1 = origin.offset vec1, factor1
	target2 = origin.offset vec2, factor2
	vec1 = origin.vector_to target1
	vec2 = origin.vector_to target2
	angle = vec1.angle_between vec2
	
	n = (angle / anglemax).round
	return [angle, origin, [vec1, vec2]] if n < 2
	
	ang_step = 1.0 / n
	
	d1 = origin.distance target1
	d2 = origin.distance target2
	lst_vec = []
	for i in 0..n
		r = i * ang_step
		d = (1 - r) * d1 + r * d2
		vec = Geom.linear_combination(r, vec2, 1-r, vec1)
		pt = G6.robust_offset origin, vec, d
		lst_vec.push origin.vector_to(pt)
	end
	[angle, origin, lst_vec]
end

#-------------------------------------------------------------
# RANDOM: Manage randomization
#-------------------------------------------------------------

#RANDOM: assign random factor to faces
def random_assign
	return unless @jpp_prop_random && @lst_blocks && option_get(:random_on)
	return if @random_assigned
	
	#Seed for random numbers
	srand option_get(:random_seed)
	
	#Assigning a random factor to each face
	rmin = option_get :randf_min
	rmax = option_get :randf_max
	@lst_blocks.each do |block|
		block.lst_pfaces.each do |pface|
			if pface.initial
				pface.rand_factor = 1.0
			else	
				r = rand
				pface.rand_factor = rmin * (1-r) + rmax * r
			end
		end
	end
	
	#Handling the coquad
	if option_get(:coquad)
		@lst_blocks.each do |block|
			hsh_faces_treated = {}
			block.lst_pfaces.each do |pface|
				pf = pface.coquad
				next unless pf && !hsh_faces_treated[pf.face_id]
				hsh_faces_treated[pf.face_id]
				pf.rand_factor = pface.rand_factor
			end
		end
	end
	
	@random_assigned = true
end

#RANDOM: Modification of the random parameters
def modify_random
	@random_assigned = false
	random_assign
	modify_execute
end

#RANDOM: toggling of the random mode
def toggle_random_mode
	option_transfer(:random_on, !(option_get :random_on))
	modify_random
end

#RANDOM: Modify the random seed
def modify_random_seed(incr)
	new_seed = option_get(:random_seed) + incr
	option_transfer :random_seed, new_seed
	option_transfer :random_on, true
	@random_assigned = false
	modify_random
end

#RANDOM: Modify the random range
def modify_random_range(symb, val)
	cur_val = option_get symb
	option_transfer symb, val
	modify_random
end

end	#class JointPushPullTool

end	#End Module F6_JointPushPull
