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

# 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			:   FredoScale_Stretch.rb
# Original Date	:   8 Mar 2009 - version 2.0
# Description	:   Implement the specific methods of the Stretch Tool of FredoScale
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_FredoScale

#--------------------------------------------------------------------------------------------------------------
# Midlle Stretching Transformation
#--------------------------------------------------------------------------------------------------------------			 				   

class TR_MidStretching

FSC_Container = Struct.new "FSC_Container", :id, :entID, :entities, :upcontainer, :tr_abs, :tr_absinv, :trup, :trup_inv,
                                            :hsplit, :hsh_edges, :active_entities, :refpos, :component, :neutralized,
											:lstatus, :subs, :bounds, :grouponent

#Initialize the stretching transformation
#  Origin: midlle of the box (i.e. independent of axes)
#  axes: array of 3 axes
# ldistance: array of box full size along the 3 axes
# offset: array of offset factors (between 0 and 1) versus the origin
def initialize(entities, origin, axes, ldistance, offset=nil, double=false)
	#Initializations
	@selection = entities
	@origin = origin
	@offset = (offset) ? offset : [0, 0, 0]
	@double = double
	set_preservation_factor
	@axes = axes.collect { |v| v.clone }
	@tr_axe = Geom::Transformation.axes origin, axes[0], axes[1], axes[2]
	@tr_axeinv = @tr_axe.inverse
	@ldistance = ldistance
	@to_recompute = true
	@tr_identity = Geom::Transformation.new
	
	precompute_all
end

def set_preservation_factor(fpreserve=0.2)
	@to_recompute = true if fpreserve != @fpreserve
	@fpreserve = fpreserve
end

def set_offset_factors(offset=nil)
	offset = [0, 0, 0] unless offset
	@to_recompute = true if offset != @offset
	@offset = offset
end

#Precompute all configuration of the geometry
def precompute_all
	@nb_container = 0
	@lst_containers = []
	@lst_compo = []
	@lst_unique = [[], [], []]
	@wireframe = [[[], []], [[], []], [[], []]]
	
	#Computing the min and max for each axes
	@lmin = []
	@lmax = []
	for iaxe in 0..2
		@lmax[iaxe] = @ldistance[iaxe] * @offset[iaxe] * 0.5
		@lmin[iaxe] = (@double) ? -@lmax[iaxe] : @lmax[iaxe]
	end	
	
	#Analyzing the geometry and hierarchy of compoenents and groups
	@top_container = create_container nil, nil, @tr_identity
	analyze_geometry @top_container
	optimize_components
	[0, 1, 2].each { |iaxe| decide_on_unique iaxe }
	@to_recompute = false
	@sense_prev = []
end

#Create and initialize a container structure
def create_container(grouponent, upcontainer, trup)
	tcomp = FSC_Container.new
	@lst_containers.push tcomp
	tcomp.id = (@nb_container += 1)
	tcomp.active_entities = G6.grouponent_entities grouponent
	tcomp.grouponent = grouponent
	tcomp.entities = (grouponent) ? tcomp.active_entities : @selection.to_a
	if grouponent.respond_to?(:definition)
		tcomp.component = grouponent
		@lst_compo.push tcomp
	end	
	tcomp.upcontainer = upcontainer
	upcontainer.subs.push tcomp if upcontainer
	tcomp.trup = trup
	tcomp.trup_inv = trup.inverse
	tcomp.hsh_edges = {}
	tcomp.lstatus = []
	tcomp.bounds = Geom::BoundingBox.new
	tcomp.neutralized = []
	tcomp.hsplit = [[[], [], []], [[], [], []], [[], [], []]]
	tcomp.subs = []
	tcomp
end

#Analyze the geometry of a component
def analyze_geometry(container)
	t = @tr_axeinv * container.trup
	hsh_edges = container.hsh_edges
	bb = container.bounds
	hsh_curve = {}
	container.entities.each do |entity|
		#Edges in natural geometry
		if entity.class == Sketchup::Edge
			next if hsh_edges[entity.entityID]
			curve = entity.curve
			if curve
				store_edges container, curve.edges, t, curve, nil, hsh_edges, hsh_curve, bb
			else	
				store_edges container, [entity], t, nil, side_curve(entity), hsh_edges, hsh_curve, bb
			end	
			
		#Component or group
		elsif entity.class == Sketchup::Group || entity.class == Sketchup::ComponentInstance
			tcomp = create_container entity, container, container.trup * entity.transformation
			analyze_geometry tcomp
			bbcomp = tcomp.bounds
			for i in 0..7
				bb.add bbcomp.corner(i)
			end	
		end	
	end	
end

#Check if an edge touches a curve - If so, return the curve, otherwise nil
def side_curve(edge)
	[edge.start, edge.end].each do |v|
		v.edges.each do |e|
			curve = e.curve
			return curve if curve
		end
	end
	nil	
end

#Store a list of associated edges with calculation of their relative position within the container
def store_edges(container, ledges, t, curve, sidecurve, hsh_edges, hsh_curve, bb)
	#Handling the curve if not already done
	if sidecurve
		lstatus2 = hsh_curve[sidecurve.to_s]
		unless lstatus2
			lstatus2 = store_edges container, sidecurve.edges, t, sidecurve, nil, hsh_edges, hsh_curve, bb
		end	
	end
	
	#Computing the position status for edges and storing it for deformation and wireframe
	twf = @tr_axe * t
	lstatus = edges_min_max ledges, t, bb
	if sidecurve
		for i in 0..2
			lstatus[i] = lstatus2[i] if lstatus[i] < 2
		end
	end	
	lstatus.each_with_index do |status, iaxe|
		next unless status
		container.hsplit[iaxe][status] += ledges #unless sidecurve
		if status < 2
			lwf = @wireframe[iaxe][status]
			ledges.each { |e| lwf.push twf * e.start.position, twf * e.end.position }
		end	
	end	
	
	#Marking the edges and curve as treated
	ledges.each { |e| hsh_edges[e.entityID] = lstatus } 
	hsh_curve[curve.to_s] = lstatus if curve
	lstatus
end

#Compute the relative position 0, 1 or 2 for a family of associated edges
def edges_min_max(lst_edges, t, bb)
	lstatus = []
	lst_edges.each do |edge|
		ptbeg = t * edge.start.position
		ptend = t * edge.end.position
		bb.add ptbeg, ptend
		lbeg = ptbeg.to_a
		lend = ptend.to_a
		[0, 1, 2].each do |iaxe|
			next unless iaxe
			vmin = @lmin[iaxe]
			vmax = @lmax[iaxe]
			if lbeg[iaxe] < vmin && lend[iaxe] < vmin
				status = 1
			elsif lbeg[iaxe] >= vmax && lend[iaxe] >= vmax
				status = 0
			else
				status = 2
			end
			lstatus[iaxe] = status unless lstatus[iaxe]
			if status != lstatus [iaxe]
				lstatus [iaxe] = 2
				break
			end	
		end
	end
	lstatus
end

#Compute which components need to be make unique for a given direction iaxe
def decide_on_unique(iaxe)
	lunique = @lst_unique[iaxe]
	@lst_containers.each do |c|
		next if c.neutralized[iaxe]
		compo = c.component
		if compo
			lunique.push compo if compo.definition.count_instances > 1
		elsif c.grouponent && c.grouponent.class == Sketchup::Group
			lunique.push c.grouponent unless G6.is_group_unique?(c.grouponent)
		end	
	end	
end		

#Determine if some components need to be made unique for the given direction
def need_make_unique?(iaxe)
	(@lst_unique[iaxe].length > 0)
end

#Perform a Make unique on all concerned components for the given direction
def proceed_make_unique(iaxe)
	return if @lst_unique[iaxe].length == 0
	@lst_unique[iaxe].each do |c|
		c.make_unique
	end	
	precompute_all
	@lst_unique[iaxe].each do |c|
		c.make_unique
	end	
	precompute_all if @lst_unique[iaxe].length > 0
	#proceed_make_unique(iaxe) if @lst_unique[iaxe].length > 0	
end

#Optimize the move of components. If they are on one side of the middle limit, then treat them as a whole
# in their up container
def optimize_components
	#Checking if components are on one side or the other of the middle points
	@lst_compo.reverse.each do |container|
		upcontainer = container.upcontainer
		next unless upcontainer
		compo = container.component
		hsplit = container.hsplit
		for iaxe in 0..2
			len0 = hsplit[iaxe][0].length
			len1 = hsplit[iaxe][1].length
			len2 = hsplit[iaxe][2].length
			next if len0 + len1 + len2 == 0
			if len2 > 0 || (len0 * len1) > 0
				if dim_container(container, iaxe) < @fpreserve * @ldistance[iaxe]
					container.neutralized[iaxe] = true
				end	
				upcontainer.hsplit[iaxe][2].push compo
			elsif len0 == 0
				container.neutralized[iaxe] = true
				upcontainer.hsplit[iaxe][1].push compo
			else
				container.neutralized[iaxe] = true
				upcontainer.hsplit[iaxe][0].push compo
			end	
		end	
	end	
	
	#Populating the neutralized status
	@lst_compo.each do |container|
		upcontainer = container.upcontainer
		for iaxe in 0..2
			container.neutralized[iaxe] = true if upcontainer.neutralized[iaxe]
		end	
	end	
end

#Return the dimension of a container along a given direction
def dim_container(container, iaxe)
	bb = container.bounds
	pt1 = bb.corner 0
	pt2 = bb.corner [1, 2, 4][iaxe]
	pt1.distance(pt2)
end

#Perform the deformation for the given entities, along the axes given
def deform(iaxe, sense, vec)

	#Checking if recomputation is required
	cursense = (sense[0] * sense[1]).abs
	if @to_recompute || (@offset[iaxe] != 0 && @sense_prev[iaxe] && @sense_prev[iaxe] != cursense)
		@double = (cursense != 0)
		precompute_all
	end	
	@sense_prev[iaxe] = cursense
	
	#Computing the translation vector
	deltavec = []
	for i in 0..1
		deltavec[i] = (sense[i] == 0) ? nil : ((sense[i] < 0) ? vec.reverse : vec)
	end
	
	#Storing the position of container to avoid moving them twice
	@lst_containers.each do |container| 
		next if container.neutralized[iaxe]
		g = container.component
		container.refpos = g.bounds.center if g
	end	
	
	#Deforming each containers
	@lst_containers.each { |container| deform_container(container, iaxe, sense, deltavec) }
end

#Precompute the deformation for teh given entities, along the axes given
def get_wireframe(iaxe, sense, vec)
	lwf = []
	for i in 0..1
		next if sense[i] == 0
		deltavec = (sense[i] < 0) ? vec.reverse : vec
		t = Geom::Transformation.translation deltavec
		lwf += @wireframe[iaxe][i].collect { |pt| t * pt }
	end
	lwf
end

#deform one container
def deform_container(container, iaxe, sense, deltavec)
	g = container.component
	tinv = container.trup_inv
	
	#Avoid deforming component that have already moved (when glued)
	return if g && container.refpos != g.bounds.center
	
	for i in 0..1
		next unless deltavec[i]
		t = Geom::Transformation.translation tinv * deltavec[i]
		container.active_entities.transform_entities t, container.hsplit[iaxe][i]
	end	
end

end	#class TR_MidStretching

end	#End Module F6_FredoScale