=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			:   CurviloftMorph.rb
# Original Date	:   04 Apr 2009
# Type			:   Sketchup Tools
# Description	:   Morphing between contours for Curviloft
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_Curviloft
					
class CVL_LoftAlgo 

#----------------------------------------------------------------------------
#  Prepare Links for Matching
#----------------------------------------------------------------------------

#Compute the morphing between the plates of the links
def morph_prepare_link(link)
	morph_link_normalize link
	morph_link_orientate link
	link_create_nodes link
	morph_compute_hard_twin link
	morph_twisting_configuration link
	morph_compute_auto_twin link
end
	
#Normalize the link plates by best fit box in 2D	
def morph_link_normalize(link)
	plate1 = link.plates[0]
	plate2 = link.plates[1]
	plane1 = plate1.plane
	plane2 = plate2.plane
	normal1 = plate1.normal
	normal2 = plate2.normal
		
	angle_offset = link.angle_offset	
	tr_angle = (angle_offset && normal2.valid?) ? Geom::Transformation.rotation(plate2.bary, normal2, angle_offset) : @tr_id
	
	#Adjust the plates to the same center
	vecbary21 = plate2.bary.vector_to plate1.bary
	tr_bary = (vecbary21.valid?) ? Geom::Transformation.translation(vecbary21) : @tr_id
	if normal1.samedirection?(normal2)
		tr_rot = @tr_id
	elsif normal1.parallel?(normal2)
		ptproj = plate2.bary.project_to_plane [plate1.bary, normal1]
		vecrot = plate1.bary.vector_to ptproj
		tr_rot2 = Geom::Transformation.rotation plate2.bary, normal1, Math::PI
		if vecrot.valid?
			tr_rot = tr_rot2 * Geom::Transformation.rotation(plate2.bary, vecrot, Math::PI)
		else
			tr_rot = tr_rot2
		end	
	else	
		angle = normal1.angle_between normal2
		vecrot = normal1 * normal2
		tr_rot = Geom::Transformation.rotation plate1.bary, vecrot, -angle
	end

	#Projection on Horizontal plane
	axes = normal1.axes
	tr_axes_inv = Geom::Transformation.axes plate1.bary, axes[0], axes[1], axes[2]
	tr_axes = tr_axes_inv.inverse
	link.axes = axes

	#Compute the transformation by Best box fitting
	morph_compute_transformation link, [tr_axes, tr_axes * tr_rot * tr_bary * tr_angle]
end

#Compute the best fit box transformation
def morph_compute_transformation(link, trs)
	link.pts_morph = []
	link.tr_morphs = []
	link.tr_basic = []
	link.twist_dangles = []
	link.twist_rangles = []
	link.twist_nbmax = []
	
	pi2 = Math::PI * 0.5
	langles = [[], []]
	pts_new = []
	
	#Computing the angles for all best fitting boxes
	lsmallest = []
	for iplate in 0..1
		tr = trs[iplate]
		link.tr_basic[iplate] = tr
		plate = link.plates[iplate]
		plane = plate.plane
		pts_new[iplate] = plate.pts.collect { |pt| tr * pt.project_to_plane(plane) }
	
		smallest = Traductor::BestFit2d.best_fitting_boxes Z_AXIS, pts_new[iplate], true
		lsmallest[iplate] = smallest
		
		compute_twist_angle link, iplate, smallest.length
	end	
	
	#Finding the minimu rotation to align boxes
	angles_min = find_smallest_angles(lsmallest[0], lsmallest[1])
	
	#Rotating the points and computing the transformations
	for iplate in 0..1		
		angle = angles_min[iplate]
		tr_rot = (angle != 0) ? Geom::Transformation.rotation(ORIGIN, Z_AXIS, -angle) : @tr_id
		pts_new[iplate] = pts_new[iplate].collect { |pt| tr_rot * pt }
		
		#Best fitting box
		bb = Geom::BoundingBox.new.add pts_new[iplate]	
		vec = bb.center.vector_to ORIGIN
		tr_dec = (vec.valid?) ? Geom::Transformation.translation(vec) : @tr_id
		pts_new[iplate] = pts_new[iplate].collect { |pt| tr_dec * pt }
		tr_box = tr_dec * tr_rot

		#Building the transformations
		fx = (bb.width == 0) ? 1 : @dside / bb.width
		fy = (bb.height == 0) ? 1 : @dside / bb.height
		tr_scale = Geom::Transformation.scaling ORIGIN, fx, fy, 1
		link.pts_morph[iplate] = pts_new[iplate].collect { |pt| tr_scale * pt }
		link.tr_morphs[iplate] = tr_scale * tr_box * trs[iplate]
	end
end

#Determine the smallest angles for alignment of boxes
def find_smallest_angles(smallest1, smallest2)
	pi = Math::PI
	pi2 = 0.5 * pi
	pi4 = 0.25 * pi
	langlemin = [0, pi2]
	smallest1.each do |bbox1|
		vecref = bbox1[0].vector_to bbox1[1]
		vecref = X_AXIS unless vecref.valid?
		angleref = vecref.angle_between X_AXIS
		angleref = -angleref if (X_AXIS * vecref) % Z_AXIS < 0	
		smallest2.each do |bbox2|
			vec = bbox2[0].vector_to bbox2[1]
			if !vec.valid? || vecref.parallel?(vec)
				langlemin = [angleref, 0]
				break
			end
			angle = vec.angle_between vecref
			angle = -angle if ((vecref * vec) % Z_AXIS < 0)
			angle = angle.modulo(pi2)
			angle = angle - pi2 if angle > pi4
			langlemin = [angleref, angle] if angle.abs < langlemin[1].abs
		end
	end	
	langlemin[1] += langlemin[0]
	langlemin
end

#Compute the value of the twist step angle
def compute_twist_angle(link, iplate, nb)
	nb = 2 if nb < 2
	nb *= 2 if nb.modulo(2) == 0
	dangle = 360 / nb
	dangle = (dangle * 10).round / 10
	nbmax = (360 / dangle).round - 1
	link.twist_dangles[iplate] = dangle
	link.twist_rangles[iplate] = dangle.degrees
	link.twist_nbmax[iplate] = nbmax	
end

def morph_compute_twist_values(link, lnb)
	#Computing the sequence
	ltwists = []
	lim = (lnb[0] == lnb[1]) ? 0 : 1
	for k in 0..lim
		nb = lnb[k]
		nb = 2 if nb < 2
		nb *= 2 if nb.modulo(2) == 0
		dangle = 360 / nb
		dangle = (dangle * 10).round / 10
		for i in 0..nb-1
			ltwists.push [k, dangle * i]
		end	
	end
	
	#Sorting
	npref = (lnb[1] > lnb[0]) ? 1 : 0
	ltwists.sort! do |a, b| 
		comp = a[1] <=> b[1]
		(comp == 0) ? ((a[0] == npref) ? -1 : 1) : comp 
	end	
	
	#Eliminating redudant value
	final_twists = [ltwists[0]]
	dangle = final_twists[0][1]
	for i in 1..ltwists.length-1
		final_twists.push ltwists[i] if dangle != ltwists[i][1]
		dangle = ltwists[i][1]
	end
end

#Compute Trigo sense for Plates and Link
def morph_link_orientate(link)
	link.trigo = 1
	return if @method == :skinning || link.duplicated

	plate1 = link.plates[0]
	plate2 = link.plates[1]
	pts1 = link.pts_morph[0]
	pts2 = link.pts_morph[1]

	if plate1.loop && plate2.loop
		trigo1 = morph_plate_trigo pts1
		trigo2 = morph_plate_trigo pts2
		trigo = trigo1 * trigo2		
	elsif plate1.loop || plate2.loop
		trigo = 1
	else
		trigo = G6.curl_trigo_sense plate1.pts, plate2.pts
	end
	link.trigo = trigo
	if trigo < 0
		plate2.pts = plate2.pts.reverse
		####link.pts_morph[1] = link.pts_morph[1].reverse
	end	
end

#Compute the Trigo sense in 2D
def morph_plate_trigo(pts)	
	tr = Geom::Transformation.translation @ubox_ptmid.vector_to(ORIGIN)
	lptd = []
	pts.each_with_index do |pt, i|
		lptd.push [i, @ubox.collect { |corner| (tr * corner).distance(pt) }]
	end
	n = pts.length
	n1 = n - 1
	
	ptlim = []
	for i in 0..3
		lx = lptd.min { |a, b| a[1][i] <=> b[1][i] }
		ptlim[i] = lx[0]
	end
	ptlim.uniq!
	
	i = ptlim.rindex(ptlim.min)
	ptlim = ptlim[i..-1] + ptlim [0..i-1] unless i == 0
	
	(ptlim[1] < ptlim[2]) ? 1 : -1
end

#----------------------------------------------------------------------------
#  Compute matching hard twins for a link
#----------------------------------------------------------------------------

#Compute the hard twins for the link
def morph_compute_hard_twin(link)
	nodes1 = link.native_nodes[0]
	nodes2 = link.native_nodes[1]
	biloop = link.biloop
	link.hard_twins = []
	
	#In Skinning mode, take only the first and last
	if @method == :skinning
		twins = []
		twins.push [nodes1.first, nodes2.first, :twin_type_hard]
		twins.push [nodes1.last, nodes2.last, :twin_type_hard]
		link.hard_twins = twins
		return twins
	end
		
	#Non-closed curves - forces extremities to match
	twins = []
	unless link.biloop
		twins.push [nodes1.first, nodes2.first, :twin_type_hard]
		twins.push [nodes1.last, nodes2.last, :twin_type_hard]
		link.hard_twins = twins
		return twins
	end
	
	#Non-closed curves - forces extremities to match
	twins = []
	if link.duplicated
		twins.push [nodes1.first, nodes2.first, :twin_type_hard]
		link.hard_twins = twins
		return twins
	end
	
end

#----------------------------------------------------------------------------
#  Compute matching automatic twins for a link
#----------------------------------------------------------------------------

#Top level method to compute the auto twin pairs
def morph_compute_auto_twin(link)
	return [] unless link.hprop[:twisting_allowed]
	
	if hprop_get :option_match_best, link
		twins = morph_get_auto_twin_best link
	else
		twins = morph_get_auto_twin_natural link
	end
	
	twins
end

def morph_signature(link, key_twisting, best)
	((best) ? 'B' : 'N') + key_twisting.to_s
end

#Return the Twisting information
def morph_get_twisting_info(link)
	nb_twisting1 = hprop_get :nb_twisting1, link
	nb_twisting2 = hprop_get :nb_twisting2, link
	[[nb_twisting1, nb_twisting2].join('+'), [nb_twisting1, nb_twisting2]]
end

#Get or compute Auto twins with Best matching box
def morph_get_auto_twin_best(link)
	return [] unless link.hprop[:twisting_allowed]
	
	#Computing the twisting pairing if not already computed
	key_twisting, lnb_twisting = morph_get_twisting_info link
	
	twins = link.hbase_auto_twins[key_twisting]
	return twins if twins
	
	#Try with specified twisting
	twins = morph_compute_auto_twin_best(link, key_twisting, lnb_twisting)
	return twins if twins.length > 0 || link.brothers.length > 0
	
	#Trying with 1 more twisting
	if false && (lnb_twisting[0] + lnb_twisting[1]) == 0
		rangle_twisting1 = link.twisting_rangles[0]
		rangle_twisting2 = link.twisting_rangles[1]
		iplate = (rangle_twisting1 <= rangle_twisting2) ? 0 : 1
		nmax = link.twist_nbmax[iplate]
		[1, -1].each do |i|
			itwin = nb_twisting + i
			next if itwin.abs > nmax
			twins = link.hbase_auto_twins[nb_twisting]
			twins = morph_compute_auto_twin_best(link, itwin) unless twins
			if twins.length > 0
				hprop_set :nb_twisting, itwin, link
				link.hbase_auto_twins[key_twisting] = twins
				return twins
			end
		end	
	end
	
	#Trying to match anyway
	twins = morph_compute_auto_twin_anyway link, key_twisting, lnb_twisting, true
	link.hbase_auto_twins[key_twisting] = twins
	
	twins
end

#Get or compute auto twin with natural shape matching (no best orientation)
def morph_get_auto_twin_natural(link)
	return [] unless link.hprop[:twisting_allowed]
	
	#Computing the twisting pairing if not already computed
	key_twisting, lnb_twisting = morph_get_twisting_info link
	twins = link.hbase_auto_twins_nat[key_twisting]
	return twins if twins
		
	twins = morph_compute_auto_twin_natural link, key_twisting, lnb_twisting

	#Trying to match anyway
	if twins.length == 0
		twins = morph_compute_auto_twin_anyway link, key_twisting, lnb_twisting, false
		link.hbase_auto_twins_nat[key_twisting] = twins
	end
	
	twins
end

#Get or compute auto twin with natural shape matching (no best orientation)
def morph_get_auto_twin_anyway(link)
	return [] unless link.hprop[:twisting_allowed]
	
	#Computing the twisting pairing if not already computed
	key_twisting, lnb_twisting = morph_get_twisting_info link
	best = hprop_get :option_match_best, link
	
	if best
		twins = link.hbase_auto_twins_any[key_twisting]
	else	
		twins = link.hbase_auto_twins_any_nat[key_twisting]
	end	
	return twins if twins
		
	twins = morph_compute_auto_twin_anyway link, key_twisting, lnb_twisting, best

	twins
end

#Compute the auto_twins for a given twisting - Last resort attempt
def morph_compute_auto_twin_natural(link, key_twisting, lnb_twisting)
	twins = []
	
	#Getting the extreme points
	lex1 = morph_compute_extremes link, 0, lnb_twisting, true
	lex2 = morph_compute_extremes link, 1, lnb_twisting, true
	
	#Arranging the chain of extreme points
	twins = morph_match_extreme(link, lex1, lex2)
	
	#Storing result of calculation
	link.hbase_auto_twins_nat[key_twisting] = twins

	twins
end

#Compute the auto_twins for a given twisting - Best matching orientation
def morph_compute_auto_twin_best(link, key_twisting, lnb_twisting)	
	#Computing the extreme points
	lex1 = morph_compute_extremes link, 0, lnb_twisting
	lex2 = morph_compute_extremes link, 1, lnb_twisting

	#Matching the extreme points
	twins = morph_match_extreme(link, lex1, lex2)
	
	twins
end

#Compute the auto_twins for a given twisting - Natural orientation
def morph_compute_auto_twin_anyway(link, key_twisting, lnb_twisting, best)
	twins = []
	
	#Getting the extreme points
	signature = morph_signature link, key_twisting, best
	
	#Matching the extremes by proximity and interpolation in Best fit mode
	best = hprop_get :option_match_best, link
	if best
		twins = link.hbase_auto_twins_any[key_twisting]
		return twins if twins
		lex1 = morph_compute_extremes link, 0, lnb_twisting
		lex2 = morph_compute_extremes link, 1, lnb_twisting
		twins = morph_match_extreme_anyway link, lex1, lex2, signature
		link.hbase_auto_twins_any[key_twisting] = twins
	end
	
	if !best || twins.length == 0
		twins = link.hbase_auto_twins_any_nat[key_twisting]
		return twins if twins
		lex1 = morph_compute_extremes link, 0, lnb_twisting, true
		lex2 = morph_compute_extremes link, 1, lnb_twisting, true
		twins = morph_match_extreme_anyway link, lex1, lex2, signature	
		link.hbase_auto_twins_any_nat[key_twisting] = twins
	end
		
	twins
end

#----------------------------------------------------------------------
# Matching of extreme points
#----------------------------------------------------------------------

#Compute normalized extremities
def morph_compute_extremes(link, iplate, lnb_twisting, natural=false)
	nodes = link.native_nodes[iplate]
	
	#Mode natural or best fit
	if natural
		hsh_lex = link.lhsh_lex_nat[iplate]
		hsh_tr_twist = link.lhsh_tr_twist_nat[iplate]
		tr_morph = link.tr_basic[iplate] * link.tr_morphs[iplate].inverse
		angle = 0
	else
		hsh_lex = link.lhsh_lex[iplate]
		hsh_tr_twist = link.lhsh_tr_twist[iplate]
		tr_morph = @tr_id
		angle = nil
	end
	
	#Angle for twisting
	nb_twisting = lnb_twisting[iplate]
	twist_angle = link.twist_rangles[iplate]
	if twist_angle
		angle = nb_twisting * twist_angle if nb_twisting != 0
	else
		nb_twisting = nil
	end	
	
	#Already computed
	lex = hsh_lex[nb_twisting]
	return lex if lex
	
	#Box adjustment for twist angle
	tt = @ubox_trcenter
	hsh_tr_twist[nb_twisting] = @tr_id
	if angle
		trot = Geom::Transformation.rotation(ORIGIN, Z_AXIS, angle) * tr_morph
		pts = nodes.collect { |node| trot * node.pt_morph }
		bb = Geom::BoundingBox.new.add pts
		fx = (bb.width == 0) ? 1 : @dside / bb.width
		fy = (bb.height == 0) ? 1 : @dside / bb.height
		tr_scale = Geom::Transformation.scaling ORIGIN, fx, fy, 1
		tr_center = Geom::Transformation.translation bb.center.vector_to(ORIGIN)
		tw = tr_scale * tr_center * trot
		tt = tt * tw
		hsh_tr_twist[nb_twisting] = tw
	else
		tt = tt * tr_morph	
	end

	#Computing the points on the boundary
	prox = 0.01 * @dside
	lst_extreme = []
	ptmorphs = nodes.collect { |node| [node, tt * node.pt_morph] }
	
	ptmorphs.each do |ll|
		node, ptmorph = ll
		if (ptmorph.y).abs <= prox
			lst_extreme.push [ptmorph.x, node, ptmorph]
		elsif (ptmorph.x - @dside).abs <= prox
			lst_extreme.push [@dside + ptmorph.y, node, ptmorph]
		elsif (ptmorph.y - @dside).abs <= prox
			lst_extreme.push [3 * @dside - ptmorph.x, node, ptmorph]
		elsif (ptmorph.x).abs <= prox
			lst_extreme.push [4 * @dside - ptmorph.y, node, ptmorph]
		end
	end	
	lst_extreme.sort! { |a, b| a[0] <=> b[0] }
	
	hsh_lex[nb_twisting] = lst_extreme
	lst_extreme
end

#Perform the matching of extreme points
def morph_match_extreme(link, lex1, lex2)
	#Identifying close points
	twins = []
	lex1.each do |ll|
		dc1, node1 = ll
		ls2 = lex2.find { |a| (dc1 - a[0]).abs < 0.01 * @dside } #unless ls2
		twins.push [node1, ls2[1], :twin_type_auto] if ls2
	end
	
	#Sorting the twins
	twins.sort! { |a, b| a[0].iseg <=> b[0].iseg }
	
	#Filtering to avoid using poinst several time
	if twins.length > 1
		lbroth = [twins[0]]
		brprev1, brprev2 = twins[0]
		for i in 1..twins.length - 1
			twin = twins[i]
			br1, br2 = twin
			skip = (br1 == brprev1 || br2 == brprev2)
			brprev1 = br1
			brprev2 = br2
			lbroth.push twin unless skip
		end
		twins = lbroth
	end
	
	twins
end

#Perform the matching of extreme points
def morph_match_extreme_anyway(link, lex1, lex2, signature)
	twins = []
	
	#Choosing which plate to interpolate
	if lex1.length <= lex2.length
		iplate = 0
	else
		iplate = 1
		a = lex2
		lex2 = lex1
		lex1 = a
	end
	iother = (iplate+1).modulo(2)
	
	lex2 = lex2 + [lex2[0]] if link.plates[iother].loop
	
	#Finding the bounaries for interpolation
	n1 = lex1.length
	lptarg = []
	for i in 0..n1-1
		d1, node1, ptx1 = lex1[i]
		iprev = i-1
		inext = (i+1).modulo(n1)
		v1 = ptx1.vector_to lex1[iprev][2]
		v2 = ptx1.vector_to lex1[inext][2]
		vec = vector_bissector v1, v2
		match = morph_match_interpol_anyway link, ptx1, vec, lex2
		next unless match
		i2, ratio = match
		node2prev = lex2[i2-1][1]
		node2next = lex2[i2][1]
		ptarg = morph_chunk_interpol_anyway link, iplate, iother, node1, node2prev, node2next, ratio
		lptarg.push [node1, ptarg] if ptarg
	end	
	
	#Creating the twins
	lptarg.each do |lp|
		node1, ptarg = lp
		newnode2 = node_insert link, iother, ptarg, :transient, signature
		if iplate == 0
			twins.push [node1, newnode2, :twin_type_auto]
		else	
			twins.push [newnode2, node1, :twin_type_auto]
		end	
	end	
		
	twins
end

def vector_bissector(v1, v2)
	if v1.parallel?(v2)
		return v1 if v1.samedirection?(v2)
		return Z_AXIS * v1
	end
	Geom.linear_combination 0.5, v1, 0.5, v2
end

#Find the matching index for interpolation between extreme points
def morph_match_interpol_anyway(link, ptx1, vec1, lex2)
	lproj = []
	for i in 1..lex2.length-1
		pt2prev = lex2[i-1][2]
		pt2next = lex2[i][2]
		ptinter = Geom.intersect_line_line [ptx1, vec1], [pt2prev, pt2next]
		next unless ptinter
		if G6.point_within_segment?(ptinter, pt2prev, pt2next)
			ratio = ptinter.distance(pt2next) / pt2prev.distance(pt2next)
			lproj.push [i, ratio, ptx1.distance(ptinter)]
		end	
	end	
	return nil if lproj.length == 0
	lproj.sort! { |a, b| a[2] <=> b[2] }
	lproj[0]
end

#Calculate the point on the <iother> contour corresponding to <node1> on the <iplate> contour
def morph_chunk_interpol_anyway(link, iplate, iother, node1, node2prev, node2next, ratio)
	nodes1 = link.native_nodes[iplate]
	nodes2 = link.native_nodes[iother]
	n2 = nodes2.length
	
	iseg_prev = iseg_next = nil
	for i in 0..n2-1
		node = nodes2[i]
		iseg_prev = i if iseg_prev == nil && node.pt == node2prev.pt
		iseg_next = i if iseg_next == nil && node.pt == node2next.pt
		break if iseg_prev && iseg_next
	end	
	
	lptarg = []
	[[iseg_prev+1, iseg_next], [iseg_next+1, iseg_prev]].each do |lim|
		lmin, lmax = lim
		lmax += n2 if lmax < lmin-1
		dtot = 0
		dist = []
		for i in lmin..lmax
			k = i.modulo(n2)
			dtot += nodes2[k-1].pt.distance nodes2[k].pt
			dist[i] = dtot
		end
		
		#Interpolating the target point
		dtarg = dtot * ratio
		ptarg = nil
		for i in lmin..lmax
			k = i.modulo(n2)
			if dist[i] >= dtarg
				d = dist[i] - dtarg
				ptbeg = nodes2[k-1].pt
				ptend = nodes2[k].pt
				r = d / ptbeg.distance(ptend)
				lptarg.push Geom.linear_combination(r, ptbeg, 1-r, ptend)
				break
			end
		end
	end
	
	return lptarg[0] if lptarg.length < 2
	
	itarg = (lptarg[0].distance(node2prev.pt) < lptarg[1].distance(node2prev.pt)) ? 0 : 1
	lptarg[itarg]	
end

#----------------------------------------------------------------------
# Information methods on Twisting
#----------------------------------------------------------------------

#Return the min, max and increment for the value of Twisting angle
def vbound_twisting(code, iplate=0)
	return 0 unless @active_link
	smax = (iplate == 0) ? :nb_twisting_max1 : :nb_twisting_max2
	nmax = @active_link.hprop[smax]
	return 0 unless nmax
	case code
	when :min
		num = -nmax
	when :max
		num = nmax
	else
		num = 1
	end
	num
end

#Analyze twisting configuration (for palette)
def morph_twisting_configuration(link=nil)
	link = @active_link unless link
	status = true
	if @method == :skinning 
		status = false
	elsif !(link.biloop && link.hard_twins.length == 0)
		status = false
	end	
	link.hprop[:twisting_allowed] = status
	
	return unless status
		
	#Properties for each Twist angle
	col_red = @tcolor_plates[0]
	tangle1 = link.twist_dangles[0].to_s
	link.hprop[:twisting_box1] = '   ' + tangle1 + "\°"
	link.hprop[:twisting_tip1] = T6[:TIP_Twisting, tangle1, col_red]
	link.hprop[:twisting_dlg1] = T6[:DLG_Twisting, tangle1, col_red]
	link.hprop[:nb_twisting_max1] = link.twist_nbmax[0]
	
	col_blue = @tcolor_plates[1]
	tangle2 = link.twist_dangles[1].to_s
	link.hprop[:twisting_box2] = '   ' + tangle2 + "\°"
	link.hprop[:twisting_tip2] = T6[:TIP_Twisting, tangle2, col_blue]
	link.hprop[:twisting_dlg2] = T6[:DLG_Twisting, tangle2, col_blue]
	link.hprop[:nb_twisting_max2] = link.twist_nbmax[1]	
end

#Check if twisting is allowed currently
def morph_twisting_allowed?(iplate=nil)
	return false unless @active_link && @active_link.hprop[:twisting_allowed] && @active_link.brothers.length == 0
	((iplate == 1) && @active_link.twist_nbmax[1] == @active_link.twist_nbmax[0]) ? false : true
end

#--------------------------------------------------------------------------
# Drawing and Interactive Edition in Preview Zones
#--------------------------------------------------------------------------

#Compute the drawing instructions for the Preview zones in 2D and 3D
def morph_draw(lst_gl, dx, dy)
	case hprop_get(:preview_mode)
	when 2
		morph_draw_2d(lst_gl, dx, dy)
	when 3
		morph_draw_3d(lst_gl, dx, dy)
	end
end

#--------------------------------------------------------------------------
# 2D PREVIEW
#--------------------------------------------------------------------------
	
#Compute the instructions for the 2D Preview zone
def morph_draw_2d(lst_gl, dx, dy)
	link = @active_link
	return unless link

	#Background
	lst_gl.clear
	
	#Drawing the base contours and vertices
	morph_instruction_base_2d(link, dx, dy) unless link.instruction_base_2d
	link.instruction_base_2d.each { |a| lst_gl.push a }
	
	#Drawing the pairs
	morph_instruction_pair_2d(link) unless link.instruction_pair_2d
	link.instruction_pair_2d.each { |a| lst_gl.push a }

	#Drawing the current vertex
	morph_instruction_current_vertex_2d(lst_gl) if @current_vertex
end

#Base contours for 2D Preview
def morph_instruction_base_2d(link, dx, dy)
	lst_gl = []
	
	#Background
	lst_gl.push [GL_QUADS, G6.pts_square(dx / 2, dy / 2, dx /2), 'darkgray']
	pts = []
	pts.push Geom::Point3d.new(0, dy/2, 0)
	pts.push Geom::Point3d.new(dx, dy/2, 0)
	lst_gl.push [GL_LINE_STRIP, pts, 'gray', 1, '']
	pts = []
	pts.push Geom::Point3d.new(dx/2, 0, 0)
	pts.push Geom::Point3d.new(dx/2, dy, 0)
	lst_gl.push [GL_LINE_STRIP, pts, 'gray', 1, '']
		
	#Scaling factors
	ptmid = Geom::Point3d.new dx/2, dy/2, 0
	tr_dec = Geom::Transformation.translation ORIGIN.vector_to(ptmid)
	link.tr_preview_2d = []
	
	#Mirroring
	sign = morph_sign_orientation_2d link
	
	#Centering on Barycenter
	factorx = []
	factory = []
	tr_bary = []
	for k in 0..1
		bary = link.tr_morphs[k] * link.plates[k].bary
		vec = bary.vector_to ORIGIN
		tr_bary[k] = Geom::Transformation.translation vec
		vx = vec.x.abs
		vy = vec.y.abs
		ds = @dside + 2 * [vx, vy].max
		factorx[k] = sign * dx / ds	
		factory[k] = dy / ds	
	end
	fred = [0.95, 0.75]
	fred = fred.reverse if factorx[1].abs > factorx[0].abs

	#Translation and scaling transformation
	ttrs = []
	for k in 0..1
		fx = fred[k] * factorx[k]
		fy = fred[k] * factory[k]
		ttrs[k] = tr_dec * Geom::Transformation.scaling(fx, fy, 1)
	end
	
	#Overall orientation
	fangle_orient = morph_quadrant_orientation_2d link, ttrs[0]
	tr_orient = Geom::Transformation.new ptmid, Z_AXIS, Math::PI * fangle_orient * 0.5
	
	#Computing the contours
	lnb_twisting = [hprop_get(:nb_twisting1, link), hprop_get(:nb_twisting2, link)]
	
	if hprop_get :option_match_best, link
		hsh_tr_twist = link.lhsh_tr_twist
	else	
		hsh_tr_twist = link.lhsh_tr_twist_nat
	end
	
	link.tr_preview_2d_s = []
	pi2 = Math::PI * 0.5
	for k in 0..1
		tred = tr_orient * ttrs[k] * tr_bary[k]
		link.tr_preview_2d[k] = tred
		link.tr_preview_2d_s[k] = tred
		
		nodes = link.nodes[k]
		plate = link.plates[k]
		glcode = (plate.loop) ? GL_LINE_LOOP : GL_LINE_STRIP
		color = @color_plates[k]
		tt_s = nil
		
		nb_twist = lnb_twisting[k]
		tr_twist = hsh_tr_twist[k][nb_twist]
		if tr_twist
			tred2 = tr_orient * ttrs[k] * Geom::Transformation.scaling(1, 1, 1) #* tr_bary[k]
			fac = 0.9
			tt = tred2 * tr_twist
			bary = tt * link.tr_morphs[k] * link.plates[k].bary
			vec = bary.vector_to ptmid
			tb = Geom::Transformation.translation vec
			tt_s = tb * tt
			pts2d_s = nodes.collect { |node| tt_s * node.pt_morph }
			lst_gl.push [glcode, pts2d_s, color, 3, '']
			link.tr_preview_2d_s[k] = tt_s	
		end
		
		pts2d = nodes.collect { |node| tred * node.pt_morph }
		if tt_s
			lst_gl.push [glcode, pts2d, color, 1, '-']
			pts2d = pts2d_s
		else
			lst_gl.push [glcode, pts2d, color, 3, '']
		end	
		
		pt01 = Geom.linear_combination 0.5, pts2d[0], 0.5, pts2d[1]
		ltri = G6.pts_triangle pt01.x, pt01.y, 5, pts2d[0].vector_to(pts2d[1])	
		lst_gl.push [GL_TRIANGLES, ltri, color] if ltri.length > 0	
	end	
	
	#Instructions for the vertices
	morph_instruction_vert_2d(link, lst_gl)
	
	link.instruction_base_2d = lst_gl
end

#Compute the mirorring factor
def morph_sign_orientation_2d(link)
	plate = link.plates[0]
	ray = plate.bary.vector_to @view.camera.eye
	sign = (plate.normal % ray < 0) ? -1 : 1
	sign
end

#Compute the quadrant orientation to align to the screen view 
def morph_quadrant_orientation_2d(link, tt)
	ttr = (tt * link.tr_morphs[0]).inverse
	ptbox1 = @view.screen_coords(ttr * ORIGIN)
	ptbox2 = @view.screen_coords(ttr * Geom::Point3d.new(@dside, 0, 0))
	ptbox3 = @view.screen_coords(ttr * Geom::Point3d.new(0, -@dside, 0))
	
	vecbox = ptbox1.vector_to ptbox2
	anglex = vecbox.angle_between X_AXIS
	angle2x = anglex
	anglex = 2 * Math::PI - anglex if (vecbox * X_AXIS) % Z_AXIS < 0
	fanglex = 2 * anglex / Math::PI
	decx = (fanglex - fanglex.round).abs
	
	vecbox = ptbox1.vector_to ptbox3
	angley = vecbox.angle_between Y_AXIS
	angle2y = angley
	angley = 2 * Math::PI - angley if (vecbox * Y_AXIS) % Z_AXIS < 0
	fangley = 2 * angley / Math::PI
	decy = (fangley - fangley.round).abs
	
	(decy < decx) ? fangley.round : fanglex.round
end

#Drawing Instructions for the twin pairs for the 2D Preview
def morph_instruction_pair_2d(link)
	lgl = []
	if link.tr_preview_2d_s != link.tr_preview_2d
		morph_instruction_pair_2d_one link, lgl, link.tr_preview_2d_s, 2, ''
		morph_instruction_pair_2d_one link, lgl, link.tr_preview_2d, 1, '-'
	else
		morph_instruction_pair_2d_one link, lgl, link.tr_preview_2d, 2, ''
	end	
	link.instruction_pair_2d = lgl
end

def morph_instruction_pair_2d_one(link, lgl, tt, wid, stipple)
	tt0 = tt[0] * link.tr_morphs[0]
	tt1 = tt[1] * link.tr_morphs[1]
	link.lbz_pairs.each do |pair|
		pts = pair[0]
		type = pair[1]
		if type == :twin_type_auto
			color = @color_twin_auto
		elsif type == :twin_type_hard
			color = @color_twin_hard
		elsif type == :twin_type_forced
			color = @color_twin_forced
		else
			next
		end	
		pt1 = tt0 * pts[0].project_to_plane(link.plates[0].plane)
		pt2 = tt1 * pts[-1].project_to_plane(link.plates[1].plane)

		lgl.push [GL_LINE_STRIP, [pt1, pt2], color, wid, stipple]
	end
end

#Compute the coordinates of the vertex in 2D preview
def morph_instruction_vert_2d(link, lst_gl)
	tt = link.tr_preview_2d_s
	link.lverts.each do |vert|
		iplate = vert.iplate
		type = vert.type
		if type == :link_type_interpol
			next
		elsif type
			color = color_from_type type
		elsif vert.node
			color = @color_line_active
		else
			color = @color_line_passive
		end
		pt = vert.pt.project_to_plane link.plates[iplate].plane
		pt = tt[iplate] * link.tr_morphs[iplate] * pt

		pts = G6.pts_square pt.x, pt.y, 2
		lst_gl.push [GL_QUADS, pts, color]
		vert.square2d = pts
	end
end

#Compute the coordinates of the vertex in 2D preview
def morph_instruction_current_vertex_2d(lst_gl)
	link = @active_link
	iplate = @current_vertex.iplate
	tt = link.tr_preview_2d
	pts = link.lbz_pts[@current_vertex.ibz]
	pt1 = pts[0].project_to_plane link.plates[0].plane
	pt1 = tt[0] * link.tr_morphs[0] * pt1
	pt2 = pts[-1].project_to_plane link.plates[1].plane
	pt2 = tt[1] * link.tr_morphs[1] * pt2

	lst_gl.push [GL_LINE_STRIP, [pt1, pt2], @color_current_vertex, 3, '']

	ptvert = (@current_vertex.iplate == 0) ? pt1 : pt2
	pts = G6.pts_square ptvert.x, ptvert.y, 4
	lst_gl.push [GL_QUADS, pts, @color_current_vertex]
end

#--------------------------------------------------------------------------
# 2D PREVIEW
#--------------------------------------------------------------------------
	
#Build drawing instructions for 3D preview	
def morph_draw_3d(lst_gl, dx, dy)
	link = @active_link
	return unless link
	
	#Drawing the plates
	lst_gl.clear
	morph_instruction_base_3d(link, dx, dy) unless link.instruction_base_3d
	link.instruction_base_3d.each { |a| lst_gl.push a }

	#Drawing the pairs
	morph_instruction_pair_3d(link) unless link.instruction_pair_3d
	link.instruction_pair_3d.each { |a| lst_gl.push a }
	
	#Drawing current vertex if any
	if @current_vertex
		morph_instruction_current_vertex_3d lst_gl
	end	
end

#Compute transformation for 3D preview
def morph_draw_transformation_3d(link, dx, dy)
	#Contours
	nodes1 = link.nodes[0]
	lpts = []
	lpts[0] = nodes1.collect { |node| @view.screen_coords node.pt }
	glcode1 = (link.plates[0].loop) ? GL_LINE_LOOP : GL_LINE_STRIP
	
	nodes2 = link.nodes[1]
	lpts[1] = nodes2.collect { |node| @view.screen_coords node.pt }
	glcode2 = (link.plates[1].loop) ? GL_LINE_LOOP : GL_LINE_STRIP

	#Bounding box for contours and junctions
	bb = Geom::BoundingBox.new.add(lpts[0] + lpts[1])
	link.lbz_pts.each { |pts| bb.add pts.collect { |pt| @view.screen_coords(pt) } }
	
	#Computing the Transformation 3D -> 2D
	w = bb.width.to_f
	h = bb.height.to_f
	
	ptmid = Geom::Point3d.new dx * 0.5, dy * 0.5, 0
	d = [w, h].max
	factor = 0.95 * dx / d.to_f
	tv = Geom::Transformation.translation bb.center.vector_to(ORIGIN)
	ts = Geom::Transformation.scaling ORIGIN, factor, -factor, 1
	to = Geom::Transformation.translation ORIGIN.vector_to(ptmid)
	tt = to * ts * tv
	
	link.tr_preview_3d = tt
end

#Compute the base instructions (plate contour + vertices) for the 3D preview
def morph_instruction_base_3d(link, dx, dy)
	lst_gl = []
	
	#Background
	lst_gl.push [GL_QUADS, G6.pts_square(dx / 2, dy / 2, dx /2), 'black']

	#Contours
	nodes1 = link.nodes[0]
	lpts = []
	lpts[0] = nodes1.collect { |node| @view.screen_coords node.pt }
	glcode1 = (link.plates[0].loop) ? GL_LINE_LOOP : GL_LINE_STRIP
	
	nodes2 = link.nodes[1]
	lpts[1] = nodes2.collect { |node| @view.screen_coords node.pt }
	glcode2 = (link.plates[1].loop) ? GL_LINE_LOOP : GL_LINE_STRIP

	#Bounding box for contours and junctions
	bb = Geom::BoundingBox.new.add(lpts[0] + lpts[1])
	link.lbz_pts.each { |pts| bb.add pts.collect { |pt| @view.screen_coords(pt) } }
	
	#Computing the Transformation 3D -> 2D
	w = bb.width.to_f
	h = bb.height.to_f
	
	ptmid = Geom::Point3d.new dx * 0.5, dx * 0.5, 0
	d = [w, h].max
	factor = 0.95 * dx / d.to_f
	tv = Geom::Transformation.translation bb.center.vector_to(ORIGIN)
	ts = Geom::Transformation.scaling ORIGIN, factor, -factor, 1
	to = Geom::Transformation.translation ORIGIN.vector_to(ptmid)
	tt = to * ts * tv
	link.tr_preview_3d = tt
	
	#Drawing for the plates contours
	eye = @view.camera.eye
	d1 = link.plates[0].bary.distance eye
	d2 = link.plates[1].bary.distance eye
	lsorder = (d1 < d2) ? [1, 0] : [0, 1]
	
	lsorder.each do |iplate|
		lpt = lpts[iplate].collect { |pt| tt * pt }
		lst_gl.push [glcode1, lpt, @color_plates[iplate], 4, '']
	end

	k = 0
	bary = @view.screen_coords link.plates[k].bary
	bary = tt * bary
	pts = G6.pts_square bary.x, bary.y, 4
	
	k = 1
	bary = @view.screen_coords link.plates[k].bary
	bary = tt * bary
	pts = G6.pts_square bary.x, bary.y, 4
	
	#Drawing for the nodes
	morph_instruction_vert_3d(link, lst_gl)
	
	#Storing the instructions
	link.instruction_base_3d = lst_gl
end

def morph_draw_axes_3d(link, lst_gl, iplate, bary2d, tt)
	colors = ['red', 'green', 'blue']
	bary3d = link.plates[iplate].bary
	size = @view.pixels_to_model 15, bary3d
	link.axes.each_with_index do |axe, i|
		pt = bary3d.offset axe, size
		pt = tt * @view.screen_coords(pt)
		lst_gl.push [GL_LINE_STRIP, [bary2d, pt], colors[i], 2, '']
	end
end

#Compute the coordinates of the vertex in 3D preview
def morph_instruction_vert_3d(link, lst_gl)
	tt = link.tr_preview_3d
	link.lverts.each do |vert|
		type = vert.type
		if type == :link_type_interpol
			next
		elsif type
			color = color_from_type type
		elsif vert.node
			color = @color_line_active
		else
			color = @color_line_passive
		end
		pt = tt * @view.screen_coords(vert.pt)
		pts = G6.pts_square pt.x, pt.y, 2
		vert.square3d = pts
		lst_gl.push [GL_QUADS, pts, color]
	end
end

#Drawing Instructions for the twin pairs
def morph_instruction_pair_3d(link)
	tt = link.tr_preview_3d
	lgl = []
	link.lbz_pairs.each do |pair|
		pts = pair[0]
		type = pair[1]
		if type == :twin_type_auto
			color = @color_twin_auto
		elsif type == :twin_type_hard
			color = @color_twin_hard
		elsif type == :twin_type_forced
			color = @color_twin_forced
		else
			next
		end	
		pts = pts.collect { |pt| tt * @view.screen_coords(pt) }
		lgl.push [GL_LINE_STRIP, pts, color, 2, '']	
	end
	link.instruction_pair_3d = lgl
end

#Compute the coordinates of the vertex in 3D preview
def morph_instruction_current_vertex_3d(lst_gl)
	link = @active_link
	tt = link.tr_preview_3d
	pts = link.lbz_pts[@current_vertex.ibz]
	pts = pts.collect { |pt| tt * @view.screen_coords(pt) }
	lst_gl.push [GL_LINE_STRIP, pts, @color_current_vertex, 3, '']

	pt = tt * @view.screen_coords(@current_vertex.pt)
	pts = G6.pts_square pt.x, pt.y, 3
	lst_gl.push [GL_QUADS, pts, @color_current_vertex]
end

end	#End Class CVL_LoftAlgo

end	#End Module F6_Curviloft
