=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Copyright 2009 - Designed November 2009 by Fredo6

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

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#-----------------------------------------------------------------------------
# Name			:   CurviloftEdition.rb
# Original Date	:   25 Nov 2009 - version 1.0
# Type			:   Sketchup Tools
# Description	:   Interactive Edition for Curviloft
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_Curviloft

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CVL_LoftAlgo: Computation class and methods for managing Visual Loft constructions
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
					
class CVL_LoftAlgo 

#----------------------------------------------------------------------------
#  Interactive Edition of Link: handling standard UI message
#----------------------------------------------------------------------------

def change_camera(view, x, y)
	return false unless @active_link
	current_quad = @active_link.lquads[@current_iquad]
	return false unless current_quad
	precision = 150
	@xcamera = x unless @xcamera
	@ycamera = x unless @ycamera
	return false if (x - @xcamera).abs < precision && (y - @ycamera).abs < precision
	target = current_quad[0]
	@xcamera = x
	@ycamera = y
	vcamera = view.camera
	view.camera = Sketchup::Camera.new(vcamera.eye, target, vcamera.up), 0.8
	refresh_when_view_changed
end

#Mouse move
def onMouseMove(flags, x, y, view)
	return false unless @lst_contours
	@x = x
	@y = y
	
	#Vertex selection and edition mode
	@mouse_link = mouse_within_link view, x, y
	if @edit_vertex
		mouse_locate_target(flags, x, y, view)
	else
		@active_link = @mouse_link unless @edit_link
		mouse_within_vertex flags, x, y, view
	end
	
	#Testing if the mouse in in the empty space
	@ip.pick view, x, y
	@mouse_in_void = (@mouse_link || @edit_vertex || [@ip.vertex, @ip.edge, @ip.face].find { |e| e }) ? false : true
	
	#Showing tooltip
	edition_show_info
	
	#Orientating the camera
	#change_camera view, x, y
	true
end

def mouse_in_void?
	@mouse_in_void
end

#Set the edit link and activate / deactivate the floating palette
def set_edit_link(status)
	@edit_link = status
end

def onLButtonDown(flags, x, y, view)
	return false unless @lst_contours
	@button_down = true
	@xdown = x
	@ydown = y
	@time_down = Time.now
	
	#Vertex edition mode
	if @current_vertex
		if @edit_vertex
			edition_exit
		else	
			edition_start
		end	
		return true
	end
	
	#Link edition mode
	set_edit_link false unless @mouse_link
	if @edit_link 
		if @active_link != @mouse_link
			@active_link = @mouse_link
		end	
	else	
		@active_link = @mouse_link
		set_edit_link((@mouse_link) ? true : false)
	end
	
	@view.invalidate
	true
end

def onLButtonUp(flags, x, y, view)
	return false unless @lst_contours
	@button_down = false
	precision = 5
	
	if @edit_vertex
		if (x - @xdown).abs > precision || (y - @ydown).abs > precision
			edition_exit
		else
			edition_start
		end
	end	
	true
end

#Double Click received
def onLButtonDoubleClick(flags, x, y, view)
	return false unless @lst_contours
	UI.beep unless @edit_vertex && @current_vertex && edition_apply_twin_remove
	edition_cancel
	@view.invalidate
	true
end

#Event Key down
def onKeyDown(key, rpt, flags, view)
	return false unless @lst_contours
	@key_down = true
	status = true
	case key
	when VK_UP
		status = history_restore true, @active_link
	when VK_DOWN
		status = history_reset true, @active_link
	when VK_LEFT
		status = history_undo true, @active_link
	when VK_RIGHT
		status = history_redo true, @active_link
	else
		@key_down = false
		return false
	end
	(status) ? @view.invalidate : UI.beep
	true
end

#Event Key up
def onKeyUp(key, rpt, flags, view)
	return false unless @lst_contours
	status = @key_down
	@key_down = false
	status
end

#Handle th ESCAPE key to cancel current editing and Undo matching
def handle_escape
	return false unless @lst_contours
	if @edit_vertex
		edition_cancel
	else
		edition_undo
	end
	true
end

def contextual_menu_contribution(cxmenu)
	return false unless @lst_contours
	edition_contextual_menu cxmenu
end

#----------------------------------------------------------------------------
#  Interactive Edition of Link and Vertex Management
#----------------------------------------------------------------------------

def edition_start
	return unless @active_link
	return if @active_link.no_edition
	@edit_vertex = true
	#@edit_link = true
	set_edit_link true
	@mode_slide = true
	@palman.auto_hide_floating true
end

#Finishing the vertex edition
def edition_exit
	@edit_vertex = false
	if @current_vertex && @point_target && @point_target != @point_beg && @point_target != @point_end
		edition_apply_move_vertex
		@current_vertex = nil
	else
		puts "NOT GOOD edition"
	end	
	@palman.auto_hide_floating false
end

def edition_cancel
	@edit_vertex = false
	@current_vertex = nil
	@palman.auto_hide_floating false
end

def edition_undo
	edition_cancel if @edit_vertex
	UI.beep unless history_undo 2, @active_link
end

#Compute the tooltip and messages
def edition_show_info
	vtooltip = ''
	ptooltip = []
	
	#Mode Editing a vertex
	if @edit_vertex
		vtooltip = "target vertex" if @point_target
		ptooltip[0] = "slide or pair with opposite"
		
	#Mode: Picking a vertex
	else
		if @current_vertex
			iseg = (@current_vertex.node) ? @current_vertex.node.iseg : '??'
			vtooltip = "current vertex #{iseg} - #{@current_vertex.pt}"
			ptooltip[0] = @msg_edition_click_cur_vertex
			ptooltip[1] = @msg_edition_see_contextual
		else
			if @active_link != @mouse_link
				vtooltip = @msg_edition_activate_link
				ptooltip[0] = vtooltip
			else
				vtooltip = ""
				ptooltip[0] = @msg_edition_select_vertex
			end	
		end	
	end
	
	@view.tooltip = vtooltip
	@palman.set_tooltip ptooltip.join("\n")
	#Sketchup.set_status_text "Mouse link = #{@mouse_link.object_id} Active_link = #{@active_link.object_id} curquad = #{@current_iquad}"	
end

#Test if the current pairing is OK
def edition_get_pairing
	return nil unless @point_target
	link = @active_link
	curvert = @current_vertex
	iplate = curvert.iplate
	iother = (iplate+1).modulo(2)
	lpt = []
	if @mode_slide
		lpt[iplate] = @point_target
		lpt[iother] = @point_beg
		lpt[2] = iother
	else
		lpt[iother] = @point_target
		lpt[iplate] = @point_end	
		lpt[2] = iplate
	end
	lpt
end

#Test if the current pairing is OK
def vertex_test_current_pairing
	@point_target_good = @point_target
	return true unless @point_target
	link = @active_link
	lpt = edition_get_pairing
	true
end

def edition_reset
	@current_vertex = nil
	@edit_vertex = nil
	@current_iquad = nil
	@current_twin = nil
	@mouse_link = nil
end

#Apply the Vertex Edition change
def edition_apply_move_vertex
	#Calculating the two matching nodes
	link = @active_link
	lpt = edition_get_pairing
	
	#Creating the intermediate nodes if needed
	if @current_twin && @current_twin[2] == :twin_type_forced
		history_register 2, link, :brother_add, lpt[0], lpt[1], @current_twin[0].pt, @current_twin[1].pt
	else
		history_register 2, link, :brother_add, lpt[0], lpt[1]
	end
end

#Apply the brother and twin removal
def edition_apply_twin_remove
	link = @active_link
	return false unless @current_vertex
	
	#Removing a Brother	
	if @current_twin && @current_twin[2] == :twin_type_forced
		history_register 2, link, :brother_remove, @current_twin[0].pt, @current_twin[1].pt
		
	#Removing a auto-twin	
	elsif @current_twin && @current_twin[2] == :twin_type_auto	
		history_register 2, link, :auto_twin_remove, @current_twin[0].pt, @current_twin[1].pt
		
	#Transforming a regular curve into a Brother	
	else
		edition_apply_make_as_brother
	end
	true
end

#Apply removal of all twins except the current one
def edition_apply_twin_remove_but
	link = @active_link
	return false unless @current_vertex
	itwin = link.eff_twins_auto.rindex @current_twin
	lpt = link.eff_twins_auto.collect { |twin| [twin[0].pt, twin[1].pt] }
	history_register 2, link, :auto_remove_but, itwin, lpt
	
	true
end

#Apply the brother and twin removal
def edition_apply_make_as_brother
	link = @active_link
	return false unless @current_vertex
			
	#Transforming a regular curve into a Brother	
	@point_target = @current_vertex.pt
	@mode_slide = true
	lpt = edition_get_pairing
	history_register 2, link, :brother_make_as, lpt[0], lpt[1]
	true
end


#Compute the contribution to contextual menu for history
def edition_contextual_menu(cxmenu)
	return unless @active_link
	link = @active_link
		
	#Vertex edition
	if @current_vertex
		cxmenu.add_sepa
		if @current_twin && @current_twin[2] == :twin_type_forced
			action = @hsh_actions[:brother_remove]
			cxmenu.add_item(action[0]) { edition_apply_twin_remove }
		elsif @current_twin && @current_twin[2] == :twin_type_auto
			action = @hsh_actions[:brother_make_as]
			cxmenu.add_item(action[0]) { edition_apply_make_as_brother }
			action = @hsh_actions[:auto_twin_remove]
			cxmenu.add_item(action[0]) { edition_apply_twin_remove }
			if link.eff_twins_auto.length > 1
				action = @hsh_actions[:auto_remove_but]
				cxmenu.add_item(action[0]) { edition_apply_twin_remove_but }
			end	
		elsif @current_twin && @current_twin[2] == :twin_type_hard	
		
		else	
			action = @hsh_actions[:brother_make_as]
			cxmenu.add_item(action[0]) { edition_apply_make_as_brother }
		end
	end
	
	#History section
	ih = link.ihistory
	nh = link.history.length
	cxmenu.add_sepa
	if nh > 0 && ih > 0
		action = link.history[ih-1][0]
		text = @mnu_edition_undo.sub('%1', @hsh_actions[action][0])
		cxmenu.add_item(text) { history_undo true, link }
	end	
	if nh > 0 && ih < nh
		action = link.history[ih][0]
		text = @mnu_edition_redo.sub('%1', @hsh_actions[action][0])
		cxmenu.add_item(text) { history_redo true, link }
	end	
	cxmenu.add_item(@mnu_edition_reset) { history_reset true, link } if ih > 0
	cxmenu.add_item(@mnu_edition_restore) { history_restore true, link } if (nh > 0 && ih < nh)
end

#Gray procedure for palette buttons
def history_gray_proc(code)
	link = @active_link
	return true unless link
	ih = link.ihistory
	nh = link.history.length
	return true if nh == 0
	case code
	when :undo, :clear
		ih == 0
	when :redo, :restore
		ih == nh
	else
		true
	end
end

#Tooltip procedure for palette buttons
def history_tip_proc(code)
	link = @active_link
	return nil unless link
	ih = link.ihistory
	nh = link.history.length
	return nil if nh == 0
	case code
	when :undo
		return nil if ih == 0
		action = link.history[ih-1][0]
		@tip_edition_undo.sub('%1', @hsh_actions[action][0])
	when :clear
		return nil if ih == 0
		@tip_edition_reset
	when :redo
		return nil if ih == nh
		action = link.history[ih][0]
		@tip_edition_redo.sub('%1', @hsh_actions[action][0])	
	when :restore
		return nil if ih == nh
		@tip_edition_restore
	else
		nil
	end
end

#Action procedure for palette buttons
def history_action_proc(code)
	link = @active_link
	return unless link
	case code
	when :undo
		history_undo true, link
	when :clear
		history_reset true, link
	when :redo
		history_redo true, link
	when :restore
		history_restore true, link
	end
end

#----------------------------------------------------------------------------
#  History Management for Vertex Edition and parameters
#----------------------------------------------------------------------------

#Register and execute an action in the history
def history_register(refresh, link, action, *args)
	laction = [action, args]
	return unless history_process_action(link, laction)
	link.history[link.ihistory..-1] = [laction]
	link.ihistory += 1
	history_refresh_after refresh, laction
end

#Undo in the history
def history_undo(refresh, link)
	return false if link == nil || link.ihistory == 0
	link.ihistory -= 1
	laction = link.history[link.ihistory]
	status = history_unprocess_action link, laction
	history_refresh_after refresh, laction
	status
end

#Undo in the history
def history_redo(refresh, link)
	return false if link == nil || link.ihistory == link.history.length
	laction = link.history[link.ihistory]
	link.ihistory += 1
	status = history_process_action link, laction
	history_refresh_after refresh, laction
	status
end

#Restore all the history
def history_restore(refresh, link)
	return false if link == nil || link.ihistory == link.history.length
	while history_redo(refresh, link) do
	end
	true
end

#Reset all the history to initial state
def history_reset(refresh, link)
	return false if link == nil || link.ihistory == 0
	while history_undo(refresh, link) do
	end
	true
end

#Refresh after action processing
def history_refresh_after(refresh, laction)
	if refresh && level = @hsh_actions[laction[0]]
		(level[1]) ? link_construct_all(true) : link_calculate_all(true)
	end	
end

#Processing actions
def history_process_action(link, laction)
	larg = laction[1]
	
	case laction[0]
	when :brother_add, :brother_make_as
		process_brother_add link, larg[0], larg[1], larg[2], larg[3]
	when :brother_remove
		process_brother_remove link, larg[0], larg[1]
	when :auto_twin_remove
		link.inv_twins.push [larg[0], larg[1]]
	when :auto_remove_but
		itwin, ltwin = larg
		keep_twin = ltwin[itwin]
		ltwin.each_with_index { |twin, i| link.inv_twins.push twin }
		process_brother_add link, keep_twin[0], keep_twin[1]
	else
		return false
	end
	true
end

#UnProcessing actions (for Undo)
def history_unprocess_action(link, laction)
	larg = laction[1]
	
	case laction[0]
	when :brother_add, :brother_make_as
		process_brother_add link, larg[2], larg[3] if larg[2] && larg[3]
		process_brother_remove link, larg[0], larg[1]
	when :brother_remove
		process_brother_add link, larg[0], larg[1]
	when :auto_twin_remove
		link.inv_twins.delete [larg[0], larg[1]]
	when :auto_remove_but
		itwin, ltwin = larg
		keep_twin = ltwin[itwin]
		ltwin.each_with_index { |twin, i| link.inv_twins.delete twin }
		process_brother_remove link, keep_twin[0], keep_twin[1]
	else
		return false
	end
	true
end

#Add a Brother 
def process_brother_add(link, pt1, pt2, ptold1=nil, ptold2=nil)
	#Retrieving or creating the new nodes
	node1 = node_insert link, 0, pt1, :brother
	node2 = node_insert link, 1, pt2, :brother
	
	#Adding the brother to the list
	link.brothers.push [node1, node2, :twin_type_forced]
	
	#Removing the unused nodes if applicable
	if ptold1 && ptold2
		process_brother_remove link, ptold1, ptold2
	end
end

#Deleting a Brother 
def process_brother_remove(link, pt1, pt2)
	node1 = link.nodes[0].find { |node| node.pt == pt1 }
	node2 = link.nodes[1].find { |node| node.pt == pt2 }
	link.brothers.delete [node1, node2, :twin_type_forced]
	link.nodes[0].delete node1 unless node1 && (node1.type != :brother || link.brothers.find { |twin| twin[0] == node1 } )
	link.nodes[1].delete node2 unless node2 && (node2.type != :brother || link.brothers.find { |twin| twin[1] == node2 } )
end

#----------------------------------------------------------------------------
#  Mouse driven Edition of vertex
#----------------------------------------------------------------------------

#Determine if the mouse is within a link and return this link
def mouse_within_link(view, x, y)
	@current_iquad = nil
	ptxy = Geom::Point3d.new x, y, 0
	eye = view.camera.eye
	@xmove = x
	@ymove = y
	
	#Filtering on the bounding box
	list_links = @lst_links.find_all { |link| link.box2d.contains?(ptxy) }
	if list_links.length == 0
		zoom_store_position
		return nil
	end	
	
	#Refining based on quads 2D
	lsquads = []
	list_links.each do |link|
		lquads2d = link.lquads2d
		for i in 0..lquads2d.length-1
			if Geom.point_in_polygon_2D(ptxy, lquads2d[i], true)
				quad = link.lquads[i]
				d = quad_min_distance(eye, quad)
				lsquads.push [link, i, quad, d]
			end	
		end
	end
	return nil if lsquads.length == 0
	
	#Sorting the links by proximity to view camera
	lsquads.sort! { |a, b| a[3] <=> b[3] }
	
	#Testing visibility against the model
	return nil unless quad_is_visible(lsquads[0][2])
	
	#Keeping the results
	link = lsquads[0][0]
	zoom_store_position lsquads[0][2] 
	@current_iquad = lsquads[0][1]
	return link
end

#Determine if the mouse is close to avretex
def mouse_within_vertex(flags, x, y, view)
	@current_vertex = nil
	@point_beg = nil
	@point_end = nil
	@point_target = nil
	@point_target_good = nil
	@mode_slide = false
	@pix_proximity = 15
	return if @active_link && @active_link.no_edition
	ptxy = Geom::Point3d.new x, y, 0
	
	#Checking vertex in current link if it exists and otherwise in all links
	lsinfo = []
	lst_link = (@active_link) ? [@active_link] : @lst_links
	lst_link.each do |link|
		link.lverts.each do |vert|
			next if vert.skip
			pt1 = vert.pt_2d
			pt2 = vert.pt2_2d
			d = pt1.distance ptxy
			if pt1 == ptxy || (d < @pix_proximity && pt1.vector_to(ptxy) % pt1.vector_to(pt2) >= 0)
				if @current_iquad == nil || link.lquads[@current_iquad] == nil || link.lquads[@current_iquad].include?(vert.pt)
					lsinfo.push [vert, link, d]
				end	
			end
		end
	end	
	return if lsinfo.length == 0
	
	#Sorting the  information and getting the most likely vertex
	lsinfo.sort! { |a, b| a[2] <=> b[2] }
	
	#Calculating selected vertex
	info = lsinfo[0]
	vert = info[0]
	link = info[1]
	@current_vertex = vert	
	@active_link = link	
	
	#Current twin if any
	pts = link.lbz_pts[vert.ibz]	
	@current_twin = link.twins.find { |twin| twin[0].pt == pts[0] && twin[1].pt == pts[-1] }
	
	#Junction curves
	if (vert.iplate == 0)
		@point_beg = pts[-1]
		@point_end = pts[0]
	else
		@point_beg = pts[0]
		@point_end = pts[-1]
	end
	
	#Computing the valid operation
	@current_othernode = link.nodes[(vert.iplate + 1).modulo(2)].find { |node| node.pt == @point_beg }
	@current_valid_ops = ''
	@current_valid_ops += 'O' if @current_vertex.node
	@current_valid_ops += 'S' if @current_othernode
	@current_valid_ops += 'R' if @current_twin
end

#Compute the target according to Mouse position in Edition mode
def mouse_locate_target(flags, x, y, view)
	#initialization
	@point_target = nil
	@mode_slide = false
	@pt_xy = Geom::Point3d.new x, y, 0
	
	#Checking if mouse is close to a plate of the link
	return nil unless @current_vertex
	mouse_close_to_plate @current_vertex.link
	
	#Testing if the target point is valid
	vertex_test_current_pairing	
end

#Determine the trget point in sliding mode
def mouse_close_to_plate(link)
	stickiness = @stickiness
	
	#Testing if the mouse is close to the plates in 2D
	lsinter = []
	for iplate in 0..1
		lverts = link.lverts.find_all { |vert| vert.iplate == iplate }
		pts = lverts.collect { |vert| vert.pt }
		pts += [pts.first] if link.plates[iplate].loop
		pts2d = pts.collect { |pt| coords_2d(pt) }
		
		for i in 0..pts2d.length-2
			pt1 = pts2d[i]
			pt2 = pts2d[i+1]
			ptproj = @pt_xy.project_to_line [pt1, pt2]
			ptproj = pt1 if ptproj.distance(pt1) < stickiness
			ptproj = pt2 if ptproj.distance(pt2) < stickiness
			if ptproj == pt1 || ptproj == pt2 || ptproj.vector_to(pt1) % ptproj.vector_to(pt2) < 0
			#if ptproj == pt1 || ptproj == pt2 || ptproj.vector_to(pt1) % pt1.vector_to(pt2) > 0
				d = @pt_xy.distance(ptproj)
				ratio = pt1.distance(ptproj) / pt1.distance(pt2)
				lsinter.push [iplate, i, ptproj, lverts[i], d, ratio] if d < @precision_edit
			end	
		end
	end
	return nil if lsinter.length == 0
	
	#Estimating where the 3D point is, by linear interpolation
	eye = @view.camera.eye
	lbz_pts = link.lbz_pts
	 
	lbz_pts = link.lbz_pts
	nbz = lbz_pts.length
	lsgood = []
	lsinter.each do |inter|
		iplate = inter[0]
		i = inter[1]
		pt2d = inter[2]
		vert = inter[3]
		d = inter[4]
		iquad = nbz * vert.ibz + iplate * (nbz - 1)
		ratio = nil
		
		#Mouse outside the link
		if @mouse_link != @active_link || @current_iquad == nil
			status = -1
		
		else
			#Mouse within the same quad
			iqbz = find_bz_from_quad link, @current_iquad
			if iqbz[0] == vert.ibz
				status = 0
				iquad = @current_iquad #if @current_iquad && link.lquads[@current_iquad]
				
			#Mouse not within the same quad --> discard
			else
				next
			end
		end	
		
		#Relative position ratio in the target quad
		if status == 0
			quad = link.lquads[iquad]
			quad2d = quad.collect { |pt| coords_2d pt }
			ratio = (status == 0) ? ratio_linear_quad(quad2d, @pt_xy.x, @pt_xy.y) : inter[5]
		else
			ratio = inter[5]
		end
		next unless ratio && ratio > -0.1 && ratio <= 1.1
		
		#Calculating the target position in 3D
		ibz1 = vert.ibz
		ibz2 = (ibz1 + 1).modulo(nbz)
		pt1 = lbz_pts[ibz1][-iplate]
		pt2 = lbz_pts[ibz2][-iplate]
		ptmid = Geom.linear_combination 0.5, pt1, 0.5, pt2
		
		pt = Geom.linear_combination 1.0 - ratio, pt1, ratio, pt2
		pt2d = @view.screen_coords(pt)
		if pt2d.distance(@view.screen_coords(pt1)) <= stickiness
			pt = pt1
		elsif pt2d.distance(@view.screen_coords(pt2)) <= stickiness
			pt = pt2
		elsif pt2d.distance(@view.screen_coords(ptmid)) <= stickiness
			pt = ptmid
		end	
		inter[6] = pt
		inter[7] = status
		
		#checking visibility against model
		pt2d = coords_2d pt
		ptface = real_input_point_when_face @view, pt2d.x, pt2d.y
		if status != 0 && ptface && really_lower(eye.distance(ptface), eye.distance(pt))
			next
		end	
		
		#Checking visibility against loft shape
		if status == -1 && @current_iquad
			quad = @mouse_link.lquads[@current_iquad]
			ptquad = quad_intersect quad, @view, pt2d.x, pt2d.y
			unless really_greater(eye.distance(ptquad), eye.distance(pt))
				next
			end	
		end	
		
		#Keeping the Intersection
		lsgood.push inter
	end
	
	#Sorting targets
	return nil if lsgood.length == 0
	inter_ok = lsgood.find { |inter| inter[7] == 0 }
	unless inter_ok
		lsgood.sort! { |a, b| a[4] <=> b[4] }
		inter_ok = lsgood[0]
	end
	
	#Finalizing the result
	@mode_slide = (@current_vertex.iplate == inter_ok[0])
	@point_target = inter_ok[6]
end

#----------------------------------------------------------------------------
#  Utility methods for Mouse interactive edition
#----------------------------------------------------------------------------

#Check if a quad is hidden by some part of the model
def quad_is_visible(quad)
	ptface = real_input_point_when_face @view, @x, @y
	return true unless ptface
	ptquad = quad_intersect quad, @view, @x, @y
	eye = @view.camera.eye
	eye.distance(ptface)> eye.distance(ptquad)
end

#Compute the intersection point between the view camera ray and the quad
def quad_intersect(quad, view, x, y)
	pickray = view.pickray x, y
	plane = Geom.fit_plane_to_points quad
	(plane) ? Geom.intersect_line_plane(pickray, plane) : quad[0]
end

#Compute the minimum distance to the corners of a quad
def quad_min_distance(eye, quad)
	ls = quad.collect { |pt| eye.distance(pt) }
	ls.sort! { |a, b| a <=> b }
	ls[0]
end

#Comparision 'Lower than' with tolerance
def really_lower(d1, d2)
	return false if d1 > d2 || d2 == 0
	((d2 - d1) / d2 < 0.05) ? false : true
end

#Comparision 'Greater than' with tolerance
def really_greater(d1, d2)
	return true if d1 > d2 || d1 == 0
	((d2 - d1) / d1 < 0.05) ? true : false
end

#Compute the non-inferenced 3D point when mouse is other a face (works if face is in component)
def real_input_point_when_face(view, x, y)
	@ph.init x, y, 0
	@ph.do_pick x, y
	face = @ph.picked_face
	return nil unless face
	tc = Geom::Transformation.new
	tr = @ph.transformation_at @ph.count
	tr = tc unless tr
	path = @ph.path_at 1
	if path
		comp =  path.reverse.find { |e| e.instance_of?(Sketchup::ComponentInstance) || e.instance_of?(Sketchup::Group) }
		tc = comp.transformation if comp
	end			
	t = tr * tc
	plane = [t * face.vertices[0].position, t * face.normal]
	Geom.intersect_line_plane view.pickray(x, y), plane
end

#Find the index of a junction curve from the Quad index in the given link
def find_bz_from_quad(link, iquad)
	quad = link.lquads[iquad]
	lbz = link.lbz_pts
	m = lbz.length
	i1 = iquad / m
	i2 = (i1 + 1).modulo(m)
	[i1, i2]	
end

#Compute the linear ratio within a quad - Trust the calculation!
def ratio_linear_quad(quad, x, y)
	#ccordinates of the quads
	x0 = quad[0].x
	y0 = quad[0].y
	x1 = quad[1].x
	y1 = quad[1].y
	x2 = quad[2].x
	y2 = quad[2].y
	x3 = quad[3].x
	y3 = quad[3].y
	
	#Basic calculation
	ax = x0 - x3
	bx = x1 - x0 + x3 - x2
	ay = y0 - y3
	by = y1 - y0 + y3 - y2
	c = y - y0
	d = y0 - y1
	e = x - x0
	f = x0 - x1
	
	#Coefficient of second square equation
	a = bx * d - by * f
	b = bx * c + d * ax - f * ay - e * by
	c = ax * c - e * ay
	
	#Resolving the equation
	delta = b * b - 4 * a * c
	return nil if delta < 0
	return -c / b if a == 0
	
	sdelta = Math.sqrt delta
	s1 = (-b + sdelta) / 2.0 / a
	s2 = (-b - sdelta) / 2.0 / a
	
	#Return only the valid solution
	return s1 if s1 > -0.1 && s1 < 1.1
	return s2 if s2 > -0.1 && s2 < 1.1
	nil 
end

def set_active_link(link)
	@active_link = link
end

#----------------------------------------------------------------------------
#  Drawing methods
#----------------------------------------------------------------------------
	
#General drawing method	
def draw(view)
	return unless @lst_contours
	return unless @lst_links.length > 0
	
	draw_compute_geometry_shown
	@lst_links.each { |link| draw_link_borders view, link unless link == @active_link }
	draw_active_link_borders view
	@lst_links.each { |link| draw_link_bz view, link if link.computed }
	if @active_link && !@active_link.no_edition
		draw_link_edition view, @active_link 
	end

	draw_vertex_path view
end

#Calculate the options for geometry
def draw_compute_geometry_shown
	top_prop = hprop_get(:param_geometry)
	@lst_links.each do |link|
		prop = hprop_get(:param_geometry, link)
		prop = top_prop unless prop
		link.draw_junction = (prop =~ /L/)
		link.draw_inter = (prop =~ /I/)	
		if prop =~ /Q(\d)/
			link.draw_quads = $1.to_i
		else
			link.draw_quads = nil
		end	
	end
end

def draw_small_square(view, pt, color, dec=3)
	pts = []
	pts.push Geom::Point3d.new(pt.x - dec, pt.y - dec)
	pts.push Geom::Point3d.new(pt.x + dec, pt.y - dec)
	pts.push Geom::Point3d.new(pt.x + dec, pt.y + dec)
	pts.push Geom::Point3d.new(pt.x - dec, pt.y + dec)
	view.drawing_color = color
	view.draw2d GL_QUADS, pts
end

def draw_link_edition(view, link)
	view.line_width = 5
	view.line_stipple = ''
	view.drawing_color = 'gray'
	llines = []
	link.lverts.each { |vert| llines += vert.lthick if vert.lthick && vert != @current_vertex }
	view.draw GL_LINES, llines.collect { |pt| G6.small_offset view, pt } if llines.length > 1
	view.drawing_color = 'lightgreen'
	llines_hard = []
	llines_auto = []
	llines_forced = []
	llines_others = []
	link.lverts.each do |vert| 
		next unless vert.lthick_node && vert != @current_vertex
		type = vert.type
		case type
		when :twin_type_hard
			llines_hard += vert.lthick_node
		when :twin_type_auto
			llines_auto += vert.lthick_node
		when :twin_type_forced
			llines_forced += vert.lthick_node
		else
			llines_others += vert.lthick_node
		end	
	end	
	view.drawing_color = @color_twin_hard
	view.draw GL_LINES, llines_hard.collect { |pt| G6.small_offset view, pt }  if llines_hard.length > 1
	view.drawing_color = @color_twin_auto
	view.draw GL_LINES, llines_auto.collect { |pt| G6.small_offset view, pt }  if llines_auto.length > 1
	view.drawing_color = @color_twin_forced
	view.draw GL_LINES, llines_forced.collect { |pt| G6.small_offset view, pt }  if llines_forced.length > 1
	view.drawing_color = @color_line_active
	view.draw GL_LINES, llines_others.collect { |pt| G6.small_offset view, pt }  if llines_others.length > 1
	
	if @current_vertex
		pt = view.screen_coords @current_vertex.pt
		draw_small_square view, pt, @color_current_vertex, 4
		pts = link.lbz_pts[@current_vertex.ibz]
		view.drawing_color = @color_current_vertex
		view.line_width = 3
		view.line_stipple = ''
		view.draw2d GL_LINE_STRIP, pts.collect { |pt| view.screen_coords pt } if pts	
	end	
end

def draw_link_flat(view, link)
	view.drawing_color = 'black'
	view.line_width = 1
	view.line_stipple = ''
	view.draw GL_TRIANGLES, link.ltriangles if link.ltriangles
end

#Draw the spline junction curve
def draw_link_bz(view, link)
	#Flat links
	return draw_link_flat(view, link) if link.flat && !link.draw_junction && !link.draw_inter
	
	#Colors and widths
	link_is_active = (link == @active_link && !link.no_edition)
	if link_is_active
		stipple = ''
		col_lines = @color_line_active
		wid_hi = 2
		wid = 1
	else	
		stipple = ''
		col_lines = @color_line_passive
		wid_hi = wid = (link == @mouse_link && !@edit_vertex) ? 2 : 1
	end
	
	#Drawing the lines and polygons
	if link.draw_junction || link.draw_inter
		view.line_width = wid
		view.line_stipple = ''
		if link.draw_junction
			view.drawing_color = (@active_link == link) ? @color_line_interpol : 'black'
			link.lbz_pts.each { |lpt| view.draw GL_LINE_STRIP, lpt }
		end
		if link.draw_inter
			view.drawing_color = (@active_link == link) ? @color_line_interpol : 'black'
			n = link.lbz_pts[0].length - 2
			for i in 1..n
				lpt = link.lbz_pts.collect { |ls| ls[i] }
				lpt += [lpt[0]] if link.plates[0].loop
				view.draw GL_LINE_STRIP, lpt
			end
		end	
	else
		view.drawing_color = 'black'
		view.draw GL_TRIANGLES, link.ltriangles if link.ltriangles && link.ltriangles.length > 2
		
		if (nq = link.draw_quads)
			view.line_width = wid
			view.line_stipple = '-'
			view.drawing_color = col_lines	
			if nq == 1
				link.ldiagsq1d = link.ldiagsq1.collect { |pt| G6.small_offset view, pt } unless link.ldiagsq1d
				view.draw GL_LINES, link.ldiagsq1d if link.ldiagsq1d.length > 1
			else
				link.ldiagsq2d = link.ldiagsq2.collect { |pt| G6.small_offset view, pt } unless link.ldiagsq2d
				view.draw GL_LINES, link.ldiagsq2d if link.ldiagsq2d.length > 1
			end	
		end
		view.line_width = wid
		view.line_stipple = stipple
		view.drawing_color = col_lines	
		view.draw GL_LINES, link.llinesd if link.llines && link.llines.length > 1
	end	
	
	#drawing the highlighted lines
	view.line_stipple = stipple
	view.line_width = (link_is_active) ? wid_hi : wid
	link.lbz_pairs.each do |lpt|
		pts, type = lpt
		####view.drawing_color = (link_is_active) ? color_from_type(type) : col_lines
		col_bord = (link.draw_junction || link.draw_inter) ? 'black' : col_lines
		view.drawing_color = (link_is_active) ? color_from_type(type) : col_bord
		view.draw GL_LINE_STRIP, pts.collect { |pt| G6.small_offset view, pt }  if pts.length > 1
	end

	#Drawing the interpolated lnes
	if @active_link == link
		view.line_stipple = '_'
		view.drawing_color = @color_line_interpol
	else	
		view.line_stipple = '_'
		view.drawing_color = col_lines
	end	
	view.line_width = 1
	link.lbz_inter_d.each do |lpt|
		view.draw GL_LINE_STRIP, lpt
	end
end

#Compute the color from the type of link
def color_from_type(type)
	case type
	when :twin_type_auto
		@color_twin_auto
	when :twin_type_forced
		@color_twin_forced
	when :twin_type_hard
		@color_twin_hard
	else
		@color_twin_none
	end	
end

#Compute the color from the type of link
def width_from_type(type)
	case type
	when :twin_type_forced, :twin_type_hard
		3
	else
		2
	end	
end

#draw the contours for the link
def draw_link_borders(view, link)
	color = 'blue'
	pix = 2
	wid = 2
	view.drawing_color = color
	view.line_stipple = ''
	view.line_width = wid
	glcode = GL_LINE_STRIP
	link.hiborders.each do |border|
		pts = border.collect { |pt| G6.small_offset(view, pt, pix) }
		view.draw glcode, pts	
	end	
end

#Draw the plates contour for the active link
def draw_active_link_borders(view)
	link = @active_link
	return unless link
	pix = 3
	wid = 3
	view.line_stipple = ''
	view.line_width = wid
	glcode = GL_LINE_STRIP
	for i in 0..1
		border = link.hiborders[i]
		next unless border
		view.drawing_color = @color_plates[i]
		pts = border.collect { |pt| G6.small_offset(view, pt, pix) }
		view.draw glcode, pts	
	end	
end

#Draw the dashed path between origin and target vertex
def draw_vertex_path(view)
	return unless @edit_vertex
	
	target3d = @point_target_good
	
	if @mode_slide
		return unless target3d && @point_beg
		pt_target = view.screen_coords(target3d)
		pt_origin = view.screen_coords(@point_beg)
		color = @color_current_vertex
	else
		return unless @point_end
		pt_origin = view.screen_coords(@point_end)
		pt_target = (target3d) ? view.screen_coords(target3d) : @pt_xy
		color = @color_opposite_vertex
	end

	#drawing the mark and dashed path
	draw_small_square view, pt_target, color, 4 if target3d
	view.drawing_color = color
	view.line_width = 2
	view.line_stipple = '_'
	view.draw2d GL_LINE_STRIP, pt_target, pt_origin
end

#----------------------------------------------------------------------------
#  Zoom simulation on wireframe
#----------------------------------------------------------------------------
	
#Move the mark - Create it if not done already	
def zoom_move_mark(pt)
	#Creating the small edge if it does not exist
	entities = @model.active_entities
	unless @zoom_group && @zoom_group.valid?
		size = @view.pixels_to_model 1, pt
		@zoom_group = entities.add_group
		pt2 = ORIGIN.offset X_AXIS, size
		@zoom_edge = @zoom_group.entities.add_edges ORIGIN, pt2
		@zoom_pt = ORIGIN
		@zoom_group.hidden = false
		return
	end
	
	#Resizing the group if necessary	
	size = @view.pixels_to_model(1, pt) * 0.5
	leng = @zoom_group.bounds.diagonal
	if leng > size
		ts = Geom::Transformation.scaling size / leng
		@zoom_group.entities.transform_entities ts, @zoom_edge
	end
	
	#Moving the construction point
	t = Geom::Transformation.translation @zoom_pt.vector_to(pt)
	@zoom_pt = pt
	t = Geom::Transformation.translation @zoom_group.bounds.center.vector_to(pt)
	entities.transform_entities t, [@zoom_group]
	@zoom_group.hidden = false
end
	
#Register current mouse position and move the fake mark	
def zoom_store_position(quad=nil)
	return unless @xmove
	unless quad
		@zoom_group.hidden = true if @zoom_group && @zoom_group.valid?
		return
	end

	#Computing the 3D point under the mouse
	pt = nil
	pickray = @view.pickray @xmove, @ymove
	plane = Geom.fit_plane_to_points *quad
	if plane
		pt = Geom.intersect_line_plane pickray, plane
	elsif quad[0] != quad[1]
		lpt = Geom.closest_points pickray, [quad[0], quad[1]]
		pt = lpt[0]
	end	
	
	#Positioning the Mark
	zoom_move_mark pt if pt
end
	
#Erase the fake mark
def zoom_terminate
	@zoom_group.erase! if @zoom_group && @zoom_group.valid?
	@zoom_group = nil
end
	
def zoom_visibility(on=true)
	if @zoom_group && @zoom_group.valid?
		@zoom_group.visible = on
	end	
end
	
end	#End Class CVL_LoftAlgo

end	#End Module F6_Curviloft
