=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			:   CurviloftLoftGeometry.rb
# Original Date	:   25 Sep 2009 - version 1.0
# Type			:   Sketchup Tools
# Description	:   Generation of Geometry for Curviloft Loft
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_Curviloft
					
class CVL_LoftAlgo 

#---------------------------------------------------------------------------------------
# Generation of the Geometry
#---------------------------------------------------------------------------------------

#Top method to Launch the geometry construction
def execute_geometry(suops)
	@suops = suops
	n = compute_steps
	#geometry_sort_links
	geometry_correct_small_segments
	@suops.launch_execution(n) { step_geometry }
end

#Compute the number of steps required
def compute_steps
	n = 0
	@lst_links.each do |link| 
		n += ((link.flat) ? 1 : link.lbz_pts.length)
	end	
	n + 1
end

#Process the contours (largely needed due to issue with SU surface generation)
def geometry_curve_contour(entities)
	#Creating a group with the overlapping edges
	unless @lst_keep.empty?
		mesh = Geom::PolygonMesh.new
		@lst_keep.each { |le| mesh.add_polygon le[0], le[1], le[0] }
		g = entities.add_group
		g.entities.add_faces_from_mesh mesh, 12
		g.entities.each { |e| e.erase! if e.instance_of?(Sketchup::Face) }
		g.explode
	end
	
	#Softening the edges
	ledges = entities.find_all { |e| e.instance_of?(Sketchup::Edge) }
	ldur = ledges.find_all { |e| (e.soft? == false) && e.faces.length == 2 }
	ldur.each { |e| e.soft = true }
end

def geometry_handle_pseudoquads(entities)
	prop_border = MYDEFPARAM[:DEFAULT_Flag_EdgePropQuadBorder]
	prop_diago = MYDEFPARAM[:DEFAULT_Flag_EdgePropQuadDiagonal]

	#Setting the property of diagonals
	soft = (prop_diago =~ /S/)
	smooth = (prop_diago =~ /M/)
	hidden = (prop_diago =~ /H/)
	cast = !(prop_diago =~ /C/)
	@lst_links.each do |link|
		next if link.flat
		if link.draw_quads
			diagos = (link.draw_quads == 1) ? link.ldiagsq1 : link.ldiagsq2
		else
			diagos = link.lst_diagos
		end	
		n = diagos.length / 2 - 1
		for i in 0..n
			j = 2 * i
			edges = entities.add_edges [diagos[j], diagos[j+1]]
			edges.each { |e| e.soft = soft ; e.smooth = smooth ; e.hidden = hidden ; e.casts_shadows = cast }
		end	
	end
	
	#Settings of the border
	soft = (prop_border =~ /S/)
	smooth = (prop_border =~ /M/)
	unless soft && smooth 
		@lst_links.each do |link|
			next if link.flat
			next unless link.draw_quads
			link.lbz_pts.each do |lbz|
				edges = entities.add_edges lbz
				#edges.each { |e| e.soft = e.smooth = false }
				edges.each { |e| e.soft = soft ; e.smooth = smooth }
			end	
			n = link.lbz_pts[0].length - 1
			for i in 0..n
				pts = link.lbz_pts.collect { |lbz| lbz[i] }
				edges = entities.add_edges pts
				#edges.each { |e| e.soft = e.smooth = false }
				edges.each { |e| e.soft = soft ; e.smooth = smooth }
			end
		end
	end	
end

#---------------------------------------------------------------------------------------
# Construction of links
#---------------------------------------------------------------------------------------

#Generate a link when flat contour
def geometry_link_flat(ilink)
	link = @lst_links[ilink]
	@grp_link = @top_group
	@suops.countage
	face = @top_entities.add_face link.contours[0..-2].reverse
	face.edges.each { |e| e.soft = e.smooth = false }
	
	return [ilink+1, 0]
end

#Generate a strip of the shape
def geometry_link_bz(ilink, ibz)
	link = @lst_links[ilink]
	unless link
		register_mesh @bz_mesh if @bz_mesh
		return [nil]
	end	
	
	#Drawing just lines
	drawing_line = hprop_get(:param_geometry, link)	
	drawing_line = hprop_get(:param_geometry) unless drawing_line
	drawing_line = nil unless drawing_line =~ /I|L/i
	
	if ibz == 0
		register_mesh @bz_mesh if @bz_mesh
		@bz_mesh = Geom::PolygonMesh.new
	end
	
	#The link is flat
	return geometry_link_flat(ilink) if link.flat && !drawing_line 
	
	#Finished
	n = link.lbz_pts.length
	if ibz >= n
		return [ilink+1, 0]
	end
	
	#Drawing the given junction curve for the link
	if ibz == 0
		@grp_link = @top_group
	end	

	@suops.countage
	option_quads = link.draw_quads
	if drawing_line
		@grp_link.entities.add_edges link.lbz_pts[ibz] if drawing_line =~ /L/i
		geometry_inter_lines(link) if ibz == 0 && drawing_line =~ /I/i
	else
		m = link.lbz_pts[ibz].length-1
		iq = ibz * m
		geometry_meshed link, link.lquads[iq..iq+m-1], @grp_link.entities, option_quads
	end
	
	#return next element to draw
	[ilink, ibz+1]
end

#Generate mean lines
def geometry_inter_lines(link)
	n = link.lbz_pts[0].length - 1
	for i in 0..n
		lpt = link.lbz_pts.collect { |ls| ls[i] }
		lpt += [lpt[0]] if link.plates[0].loop
		@grp_link.entities.add_curve lpt
	end
end

def geometry_unique_points(quad)
	qd = quad.clone
	n = quad.length - 1
	for i in 0..n
		pt1 = qd[i]
		for j in i+1..n
			qd[j] = nil if pt1 && pt1 == quad[j]
		end
	end	
	qd.compact
end

#Build a Polygon Mesh for the quads
def geometry_meshed(link, lquads, entities, option_quads=nil)
	mesh = @bz_mesh
	lquads.each do |quad|	
		#unique list of points for the quad
		qd = geometry_unique_points quad
		next if qd.length < 3
		if qd.length == 3
			mesh.add_polygon(quad & qd)
			next
		end	
		
		#Checking if the 4 points can make a face
		plane = Geom.fit_plane_to_points *quad
		if plane && !quad.find { |pt| !pt.on_plane?(plane) } && !option_quads
			mesh.add_polygon quad
		elsif quad.length == 4
			if option_quads == 1
				mesh.add_polygon quad[0..2]
				mesh.add_polygon [quad[2], quad[3], quad[0]]
				link.lst_diagos.push quad[0], quad[2]
			else
				mesh.add_polygon [quad[0], quad[1], quad[3]]
				mesh.add_polygon quad[1..3]
				link.lst_diagos.push quad[1], quad[3]
			end
		else
			n = first_unaligned quad
			mesh.add_polygon quad[0..n]
			mesh.add_polygon(quad[n..-1] + [quad[0]])
			link.lst_diagos.push quad[0], quad[n]
		end	
	end
end

#Compute the index of the nex points which is not aligned with the previous ones
def first_unaligned(pts)
	vec = pts[0].vector_to pts[1]
	n = pts.length-1
	for i in 2..pts.length-1
		v = pts[i-1].vector_to pts[i]
		return i unless vec.parallel?(v)
	end
	n
end

#Main State Automat function to build the geometry
def step_geometry
	while(action, *param = @suops.current_step) != nil
	
		case action
		when :_init
			@top_group.erase! if @top_group && @top_group.valid?
			@top_group = @model.active_entities.add_group
			@top_entities = @top_group.entities
			@lst_links.each { |link| link.lst_diagos = [] }
			@meshes = []
			@bz_mesh = nil
			@imesh_num = 0
			@h_gplates = {}
			next_step = [:junctions, 0, 0]
		
		when :junctions
			return if @suops.yield?
			ilink, ibz = param
			ilink, ibz = geometry_link_bz ilink, ibz
			next_step = (ilink) ? [action, ilink, ibz] : [:commit_mesh, 0]	
		
		when :commit_mesh
			register_mesh
			next_step = [:clean_up]
					
		when :clean_up	
			geometry_curve_contour @top_entities
			geometry_handle_pseudoquads @top_entities
			next_step = [:make_curves, 0]
			
		when :make_curves
			return if @suops.yield?
			ilink, = param
			ilink = geometry_make_curves ilink
			next_step = (ilink) ? [action, ilink] : [:finish]	
			
		when :finish	
			@suops.countage
			if @top_entities.length == 0
				@suops.abort_operation
				return
			end	
			next_step = nil
		end	
		
		break if @suops.next_step(*next_step)
	end
end

#Transform the spline junctions into SU curves
def geometry_make_curves(ilink)
	link = @lst_links[ilink]
	return nil unless link
	
	t0 = Time.now.to_f
	if hprop_get(:param_geometry, link) =~ /C/i
		link.lbz_pts.each do |pts|
			grp = @top_entities.add_group
			ledges = grp.entities.add_curve pts
			ledges.each { |e| e.smooth = true ; e.soft = true }
			grp.explode
		end	
	end	
	@curves_made = true
	ilink+1
end

#Create faces from meshes
def register_mesh(mesh=nil)
	if mesh
		@imesh_num += 1
		@meshes.push mesh
	end	
	if mesh == nil || @imesh_num > 0
		entities = @top_entities
		@meshes.each do |mesh| 
			faces = entities.add_faces_from_mesh mesh, 12 
		end	
		@imesh_num = 0
		@meshes = []
	end
end

#Compute a hash key for a point
def hash_point(point)
	((point.to_a.collect { |u| sprintf("%7f", u) }).join 'a').reverse
end

#Sorting the link for drawing order so that they are all consistently oriented
def geometry_sort_links
	return @lst_links unless @method == :skinning
	nlinks = @lst_links.length
	return [] if @lst_links.length == 0
	
	lst_links = @lst_links
	lst_links.each do |link|
		link.key_corners = link.quad_corners.collect { |pt| hash_point pt }
	end
	
	lilinks = (0..nlinks-1).to_a
	igroup = 0
	
	until lilinks.empty?
		lres_links = [lilinks.shift]
		igroup += 1
		until lres_links.empty?	
			ilink0 = lres_links.shift
			lst_links[ilink0].igroup = igroup
			lscarnac0 = lst_links[ilink0].key_corners
			lik = lilinks.find_all { |i| (lscarnac0 & lst_links[i].key_corners).length > 0 }
			next if lik.empty?
			lik.each { |il| lst_links[il].igroup = igroup }
			lilinks -= lik
			lres_links += lik
		end
	end
		
	@lst_links
end

#---------------------------------------------------------------------------------------
# Correction for small segments at junctions
#---------------------------------------------------------------------------------------

#Adjust common sides of links to avoid small segments at junction
def geometry_correct_small_segments
	@lst_keep = []
	return if @lst_links.length < 2
	t0 = Time.now.to_f
	hlink_touched = {}
	
	fmin = 0.01
	
	#Preparing the curves by string transformation
	@lst_links.each do |link|
		link.borders_s = []
		link.lbz_pts_s = link.lbz_pts.collect { |lpt| lpt.collect { |pt| pt.to_s } }
		[0, 1].each do |iplate|
			link.borders_s[iplate] = link.borders[iplate].collect { |pt| pt.to_s }
		end	
	end

	#Skinning method: compare all links with any others
	nl = @lst_links.length - 1
	if @method == :skinning
		lcompare = [[0, 0], [0, -1], [-1, 0], [-1, -1]]
		for ilink1 in 0..nl
			link1 = @lst_links[ilink1]
			
			for ilink2 in ilink1+1..nl
				link2 = @lst_links[ilink2]
				geometry_correct_small_segments_compare lcompare, fmin, link1, link2, false, false, hlink_touched
				geometry_correct_small_segments_compare lcompare, fmin, link1, link2, true, true, hlink_touched
				geometry_correct_small_segments_compare lcompare, fmin, link1, link2, true, false, hlink_touched
				geometry_correct_small_segments_compare lcompare, fmin, link1, link2, false, true, hlink_touched
			end
		end
	else
		lcompare = [[1, 0]]
		for ilink in 1..nl
			link1 = @lst_links[ilink-1]
			link2 = @lst_links[ilink]
			geometry_correct_small_segments_compare lcompare, fmin, link1, link2, false, false, hlink_touched
		end	
		if hprop_get(:option_global_loop)
			link1 = @lst_links[nl]
			link2 = @lst_links[0]
			geometry_correct_small_segments_compare lcompare, fmin, link1, link2, false, false, hlink_touched
		end
	end
	
	hlink_touched.values.each { |link| link_compute_quads link }
	
end

#Perform small segment adjustmenst for 2 links
def geometry_correct_small_segments_compare(lcompare, factor_min, link1, link2, fbz1, fbz2, hlink_touched)	
	lcompare.each do |ll|
		ibz1, ibz2 = ll
		lb1 = (fbz1) ? link1.lbz_pts_s[ibz1] : link1.borders_s[ibz1]
		lb2 = (fbz2) ? link2.lbz_pts_s[ibz2] : link2.borders_s[ibz2]
		
		#Check if the two contours have points in common
		inter1 = lb1 & lb2
		lenc = inter1.length
		next if lenc < 2 || (lenc == lb1.length && lenc == lb2.length)
		
		#Real points
		lbz1 = (fbz1) ? link1.lbz_pts[ibz1] : link1.borders[ibz1]
		lbz2 = (fbz2) ? link2.lbz_pts[ibz2] : link2.borders[ibz2]
		
		#Special treatment for loops
		loop1 = (lb1[0] == lb1[-1])
		loop2 = (lb2[0] == lb2[-1])
		next if loop1 && !loop2 || !loop1 && loop2
		istart1 = istart2 = 0
		if loop1
			lb1 = lb1[0..-2]
			lb2 = lb2[0..-2]
			lbz1 = lbz1[0..-2]
			lbz2 = lbz2[0..-2]
			istart1 = lb1.rindex inter1[0]
			istart2 = lb2.rindex inter1[0]
			n1 = lb1.length - 1
			n2 = lb2.length - 1
			lb1 = lb1[istart1..n1] + lb1[0..istart1-1] if istart1 != 0
			lb2 = lb2[istart2..n2] + lb2[0..istart2-1] if istart2 != 0
			lbz1 = lbz1[istart1..n1] + lbz1[0..istart1-1] if istart1 != 0
			lbz2 = lbz2[istart2..n2] + lbz2[0..istart2-1] if istart2 != 0
			inter1 = lb1 & lb2
		else
			n1 = lb1.length - 1
			n2 = lb2.length - 1		
		end	
		
		#Checking the reverse direction and start and end of common part
		inter2 = lb2 & lb1
		ptbeg1 = inter1[0]
		ptend1 = inter1[-1]
		reverse = (ptbeg1 == inter2[-1])
		
		lb22 = (reverse) ? lb2.reverse : lb2
		lbz22 = (reverse) ? lbz2.reverse : lbz2
		if loop1
			ibeg1 = 0
			ibeg2 = 0
			iend1 = n1
			iend2 = n2	
		else
			ibeg1 = lb1.rindex ptbeg1
			ibeg2 = lb22.rindex ptbeg1
			iend1 = lb1.rindex ptend1
			iend2 = lb22.rindex ptend1	
		end
		
		#Concatening the points and sorting them by distance
		lbmix = []
		d = 0
		for i in ibeg1..iend1
			d += ((i == ibeg1) ? 0 : lbz1[i-1].distance(lbz1[i]))
			j = (istart1 + i).modulo(n1+1)
			lbmix.push [lbz1[i], nil, d, 1, j]
		end
		d = 0
		for i in ibeg2..iend2
			d += ((i == ibeg2) ? 0 : lbz22[i-1].distance(lbz22[i]))
			j = (istart2 + i).modulo(n2+1)
			k = (reverse) ? n2 - j : j
			lbmix.push [lbz22[i], nil, d, 2, k]
		end
		lbmix.sort! { |a, b| a[2] <=> b[2] }
		lbmix.each_with_index { |a, i| a[1] = i }
				
		#minimum distance
		d1 = (fbz1) ? link1.tot_len[ibz1] : link1.borders_len[ibz1]
		d2 = (fbz2) ? link2.tot_len[ibz2] : link2.borders_len[ibz2]
		dmin = factor_min * [d1, d2].min 
		
		#Merging close points
		lbz_pts2 = link2.lbz_pts
		id = link2.object_id
		status = false
		for i in 2..lbmix.length-3
			next unless lbmix[i][3] == 1
			pt = lbmix[i][0]
			ptprev = lbmix[i-1][0]
			ptnext = lbmix[i+1][0]
			lbmprev = lbmix[i-1]
			lbmcur = lbmix[i]
			lbmnext = lbmix[i+1]
			next if pt == ptprev || pt == ptnext
			
			dprev = pt.distance(ptprev)
			dnext = pt.distance(ptnext)
			lb = nil
			dd = 0
			if lbmix[i-1][3] == 2 && lbmix[i-1][0] == lbmix[i-2][0] && pt.distance(ptprev) < dmin
				lb = lbmix[i-1]
			end	
			if lbmix[i+1][3] == 2 && lbmix[i+1][0] == lbmix[i+2][0] && dnext < dmin && dnext < dd
				lb = lbmix[i+1]			
			end
			if lb
				j = lb[4]
				(fbz2) ? lbz_pts2[ibz2][j] = pt : lbz_pts2[j][ibz2] = pt
				lbz22[j] = pt
				lb22[j] = pt.to_s
				lb[0] = pt
				status = true
			end	
		end
		hlink_touched[id] = link2 if status
		
		#Identifying the standalone points
		ls_uniq = []
		for i in 1..lbmix.length-2
			ptprev = lbmix[i-1][0]
			pt, icur = lbmix[i]
			ptnext = lbmix[i+1][0]
			if pt != ptprev && pt != ptnext
				ls_uniq.push icur
			end	
		end
		
		#Constructing the list of segments
		ls_uniq.each_with_index do |icur, i|
			ptprev = lbmix[icur-1][0]
			ptcur = lbmix[icur][0]
			ptnext = lbmix[icur+1][0]
			@lst_keep.push [ptprev, ptcur]
			@lst_keep.push [ptcur, ptnext] if ptnext != ls_uniq[i+1]
		end	
		
	end	
end

end	#End Class CVL_LoftAlgo

end	#End Module F6_Curviloft

