=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Copyright 2009 - Designed September 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			:   CurviloftSkinning.rb
# Original Date	:   4 Jan 2010 - version 1.0
# Description	:   Specific method for Skinning in the Curviloft script family
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_Curviloft

class CVL_LoftAlgo 

#----------------------------------------------------------------------------
# SKINNING: analysis of Contours
#----------------------------------------------------------------------------

#Verify contours for Skin
def skinning_check_contours(lst_cells, from_selection=false)
	#Analysing the cells and creating the plates and links
	lst_plates = []
	lst_cells.each do |cell|
		#checking if cells has already faces built
		
		#checking if contour is flat
		contour = G6.curl_concat cell
		if G6.points_coplanar?(contour)
			flat = true
		end	
		
		#Building a cell with 4 sides
		rails, pplates = skinning_adjust_cell cell
		next unless rails
		
		lplates = pplates.collect { |lpt| plate_create(lpt) }
		lst_plates += lplates
		link = link_create *lplates
		
		contour = G6.curl_concat cell
		if G6.points_coplanar?(contour)
			flat = true
		end	
		link.flat = flat 
		link.no_edition = flat 
		link.contours = contour
		link.hiborders = [contour]
		link.rails_raw = rails
		link.rails_native = G6.curl_harmonize rails[0], rails[1], 0.025
		link.rails_tot_len = link.rails_native.collect { |rail| G6.curl_length rail }
	end

	#Final step
	@lst_plates = lst_plates
	
	true
end

#Swap the rail and plates
def skinning_swap_rail(link=nil)
	link = @active_link unless link
	return unless link

	old_pts_rails = link.rails_raw.clone
	old_pts_plates = link.plates.collect { |plate| plate.pts }	
	
	#Triangular shape - Revert the position of the punctual rail
	if old_pts_rails[0].length == 1
		new_pts_plates = [old_pts_rails[1].clone, old_pts_plates[0].reverse]
		new_pts_rails = [[old_pts_plates[0].last], old_pts_plates[1].reverse]	
		new_pts_plates[1] = new_pts_plates[1].reverse unless new_pts_plates[1].first == new_pts_plates[0].first
		new_pts_rails[1] = new_pts_rails[1].reverse if new_pts_rails[1].first != new_pts_plates[0].last
		
	#4-sides shape	
	else
		new_pts_plates = old_pts_rails.collect { |rail| rail.clone }
		new_pts_rails = old_pts_plates
	end
	
	#resetting the edition
	edition_reset
	
	#Assigning the new rails and plates and recalculating
	for i in 0..1
		plate_reset link.plates[i], new_pts_plates[i]
	end	
	link_reset link
	link_associate_plates link, link.plates
	link.rails_raw = new_pts_rails
	link.rails_native = G6.curl_harmonize new_pts_rails[0], new_pts_rails[1], 0.025
	link.rails_tot_len = link.rails_native.collect { |rail| G6.curl_length rail }
	link_prepare link
	link_calculate_all true
end

def skinning_swap_forbidden?
	@active_link && @active_link.flat
end

#Construct a cell with 4 or 3 sides
def skinning_adjust_cell(cell)
	case cell.length
	when 4
		#do nothing
	when 1, 2, 3
		cell = skinning_split_cells_by_four(cell)
	else
		return nil
	end	
	return nil unless cell 
	
	case cell.length
	when 3
		return skinning_split_cells_by_three(cell)
	
	when 4
		n1 = cell[0].length + cell[2].length
		n2 = cell[1].length + cell[3].length
		if n1 > n2
			pplates = [cell[0], cell[2].reverse]
			rails = [cell[3].reverse, cell[1]]
		else
			pplates = [cell[1], cell[3].reverse]
			rails = [cell[0].reverse, cell[2]]
		end
		
	else
		return nil
	end
	
	[rails, pplates]
end

#Build a Plate / rail list from a Cell with only 3 sides
def skinning_split_cells_by_three(cell)
	ls = cell.collect { |c| [c, c.length] }
	ls.sort! { |a, b| a[1] <=> b[1] }
	cell = ls.collect { |a| a[0] }
	
	#Plates, with higher number of points
	pp1 = cell[2]
	pp2 = cell[1]
	pivot = pp1.first
	pp2 = pp2.reverse unless pp2.first == pivot
	
	#Rails
	rail = cell[0]
	rail = rail.reverse unless pp1.last == rail.first
	
	[[[pivot], rail], [pp1, pp2]]
end

#Build a Plate / rail list from a Cell with 4 sides
def skinning_split_cells_by_four(cell)
	n = cell.length
	newcell = nil
	
	if n == 1
		newcell, note = skinning_subdivide_cell(cell[0], 4)
		return newcell
	end	

	if n == 2
		newcell = nil
		newcell02, note02 = skinning_subdivide_cell(cell[0], 2)
		newcell03, note03 = skinning_subdivide_cell(cell[0], 3)
		newcell12, note12 = skinning_subdivide_cell(cell[1], 2)
		newcell13, note13 = skinning_subdivide_cell(cell[1], 3)
		note2 = 0.5 * (note02 + note12)
		notemin = [note03, note13, note2].min
		case notemin
		when note2
			newcell = newcell02 + newcell12
		when note03
			newcell = newcell03 + [cell[1]]
		when note13
			newcell = [cell[0]] + newcell13
		end	
		return newcell
	end	
	
	if n == 3	
		newcells = []
		notes = []
		for i in 0..2
			newcells[i], notes[i] = skinning_subdivide_cell(cell[i], 2)
		end
		notemin	= notes.min
		return cell if notemin == 100
		imin = 	notes.rindex(notemin)
		newcell = []
		for i in 0..2
			if i == imin
				newcell += newcells[i]
			else
				newcell.push cell[i]
			end
		end	
		return newcell
	end
	
	newcell
end

#Subdivide a portion of cell by a number of division.
#returns a note on the adequacy
def skinning_subdivide_cell(lpt, ndiv)
	angle_min = 140.degrees
	loop = (lpt.first == lpt.last)
	nlpt = lpt.length - 1
	
	langles = []
	if loop 
		langles.push [0, lpt[0].vector_to(lpt[-2]).angle_between(lpt[0].vector_to(lpt[1]))]
	end	
	for i in 1..lpt.length-2
		langles.push [i, lpt[i].vector_to(lpt[i-1]).angle_between(lpt[i].vector_to(lpt[i+1]))]
	end	
	
	langles = langles.find_all { |a| a[1] > 0 && a[1] < angle_min }
	return [[lpt], 100] if langles.length < ndiv-1
	langles = langles.sort { |a, b| a[1] <=> b[1] }
	
	note = 0
	for i in 0..ndiv-2
		note += langles[i][1]
	end
	note = note / ndiv
	
	ndiv += 1 if loop
	lcuts = langles[0..ndiv-2].collect { |a| a[0] }
	lcuts.sort! { |a, b| a <=> b }
	unless loop
		lcuts[0,0] = 0
		lcuts.push nlpt
	end	
	
	newcell = []
	for i in 0..lcuts.length-2
		j1 = lcuts[i]
		j2 = lcuts[i+1]
		newcell.push lpt[j1..j2]
	end

	if loop
		pts = lpt[lcuts.last..nlpt] + lpt[1..lcuts[0]]
		newcell.push pts
	end	
		
	[newcell, note]
end

#Get a vertex from a given point
def get_vertex_from_point(pt)
	(@method_get_vertex) ? @method_get_vertex.call(pt) : nil
end

#Get an edge from two consecutive points
def get_edge_from_points(pt1, pt2)
	vx1 = get_vertex_from_point pt1
	return nil unless vx1
	vx2 = get_vertex_from_point pt2
	return nil unless vx2
	vx1.edges.find { |e| e.other_vertex == vx2 }
end

#Construct the Skinning curves for a link (2 pathes)
def skinning_junction_construct(link)	

	#Potential oversampling
	skinning_oversample_rail link
	
	rails = link.rails
	rail1 = rails[0]
	rail2 = rails[1]
	dtot1 = link.tot_len[0]
	dtot2 = link.tot_len[1]
	lbz_pts = []
	
	#Degraded rails when punctual
	if rail1[0] == rail1[-1]
		puts "Pucntual 1"
		link.pairs.each { |pair| lbz_pts.push G6.curl_move_extremities(rail2, pair[0].pt, pair[1].pt) }
		return lbz_pts
	end
	if rail2[0] == rail2[-1]
		puts "Pucntual 2"
		link.pairs.each { |pair| lbz_pts.push G6.curl_move_extremities(rail1, pair[0].pt, pair[1].pt) }
		return lbz_pts
	end
	
	#Averagring the curves
	d1 = 0
	d2 = 0
	ptprev1 = link.pairs[0][0].pt
	ptprev2 = link.pairs[0][1].pt
	link.pairs.each do |pair|
		pt1 = pair[0].pt
		pt2 = pair[1].pt
		d1 += pt1.distance(ptprev1)
		d2 += pt2.distance(ptprev2)
		ptprev1 = pt1
		ptprev2 = pt2
		ratio1 = (dtot1 == 0) ? 1.0 : 1.0 - d1 / dtot1
		ratio2 = (dtot2 == 0) ? 1.0 - ratio1 : d2 / dtot2
		crv1 = G6.curl_move_extremities rail1, pt1, pt2
		crv2 = G6.curl_move_extremities rail2, pt1, pt2
		
		sum = ratio1 + ratio2
		ratio1 = ratio1 / sum
		ratio2 = ratio2 / sum
		
		#Computing the variable ratio
		nc = crv1.length - 1
		dc = [0.0]
		dtot = 0.0
		for i in 1..nc
			dtot += crv1[i-1].distance(crv1[i])
			dc[i] = dtot
		end
		
		#Averaging the profiles
		pts = []
		for i in 0..nc
			rc = dc[i] / dtot
			rc = 1 - rc
			ratio = (rc * ratio1 + (1.0 - rc) * ratio2)
			ratio = 0.5 * (ratio1 + ratio2)
			sum = (ratio1 + ratio2)
			pt1 = Geom.linear_combination ratio1, crv1[i], (1.0 - ratio1), crv2[i]
			pt2 = Geom.linear_combination 1 - ratio2, crv1[i], ratio2, crv2[i]
			pts[i] = Geom.linear_combination 0.5, pt1, 0.5, pt2
		end	
		lbz_pts.push pts
	end
	
	lbz_pts
end

#Interpolate two rails (with equal number of points)
#return the two rails interpolated and the list of index of points added
def skinning_oversample_rail(link)
	#No Oversampling
	link.rails = link.rails_native
	return unless hprop_get(:option_sample, link)
	fsample = hprop_get(:factor_sample, link)
	return if fsample < 1

	#Initialization
	rail1 = link.rails_native[0]
	rail2 = link.rails_native[1]
	dtot1 = link.rails_tot_len[0]
	dtot2 = link.rails_tot_len[1]
	ratio1 = (dtot1 > 0) ? 1.0 / dtot1 : 1.0
	ratio2 = (dtot2 > 0) ? 1.0 / dtot2 : 1.0
	factor = fsample
	
	#Going through each segment of the rails
	newrail1 = [rail1[0]]
	newrail2 = [rail2[0]]
	rail_index = []
	len = rail1.length-1

	for i in 1..len
		pt1_prev = rail1[i-1]
		pt2_prev = rail2[i-1]
		pt1 = rail1[i]
		pt2 = rail2[i]
		d1 = pt1.distance(pt1_prev)
		d2 = pt2.distance(pt2_prev)
		
		n1 = (d1 * ratio1 * factor).round
		n2 = (d2 * ratio2 * factor).round
		n = [n1, n2].max
		
		step = 1.0 / (n + 1)
		for j in 1..n
			f = j * step
			newrail1.push Geom.linear_combination(1-f, pt1_prev, f, pt1)
			newrail2.push Geom.linear_combination(1-f, pt2_prev, f, pt2)
			rail_index.push newrail1.length - 1
		end		
		newrail1.push pt1
		newrail2.push pt2
	end
	
	link.rails_sample_index = rail_index
	link.rails = [newrail1, newrail2]
end

end	#End Class CVL_LoftAlgo

end	#End Module F6_Curviloft
