--	author:		fruktor
--	date:
--	version:	0.1
--	history:	0.1 - initial implementation
--
--	note:		incorporates WoodClaw3!
--
--	rights:		Free for non-commercial usage

WoodWire = {};

function WoodWire.prerequisitesPresent(specializations)
    return true;
end;

function WoodWire:load(xmlFile)
	self.triggerCallbackWire 	= WoodWire.triggerCallbackWire;
	self.attachTrunksWire 		= WoodWire.attachTrunksWire;
	self.detachTrunksWire 		= WoodWire.detachTrunksWire;
	self.rcCallbackWire 		= WoodWire.rcCallbackWire;
	self.rcCallbackWire2 		= WoodWire.rcCallbackWire2;
	self.setLoopToTrunk			= WoodWire.setLoopToTrunk;
	self.removeLoopFromTrunk	= WoodWire.removeLoopFromTrunk;
	self.addHelpButtonTextWithCorrectMouseButtons = WoodWire.addHelpButtonTextWithCorrectMouseButtons;
	
	self.ww = {};
	
	self.ww.playerInTrigger = false;
	self.ww.playerTrigger = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire#playerTriggerIndex"));
	addTrigger(self.ww.playerTrigger, "triggerCallbackWire", self);

	self.ww.trunkTrigger = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire#trunkTriggerIndex"));
	addTrigger(self.ww.trunkTrigger, "triggerCallbackWire", self);	
	self.ww.triggerTrunks = {};	
	self.ww.trunkAttachCompIndex = getXMLInt(xmlFile, "vehicle.woodWire#trunkAttachCompIndex");	

	self.ww.attachedTrunks = {};	
	self.ww.joint1Node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.woodWire#trunkJointIndex1"));
	self.ww.joint1Attached = false;
	self.ww.joint2Node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.woodWire#trunkJointIndex2"));
	self.ww.joint2Attached = false;	
	local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.woodWire#jointRotationLimit"));
	self.ww.trunkJointRotLimit = {math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0))}
	local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.woodWire#jointTranslationLimit"));
	self.ww.trunkJointTransLimit = {Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0)}
	
	self.ww.rc = {};
	self.ww.rc.nodeUp = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.rcNodesUD#up") );
	self.ww.rc.nodeDown = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.rcNodesUD#down") );
	self.ww.rc.maxX = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesUD#maxX");
	self.ww.rc.minX = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesUD#minX");
	self.ww.rc.maxDistV = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesUD#maxDist");
	self.ww.rc.resolution = 0.05;
	self.ww.rc.countV = math.ceil( (self.ww.rc.maxX - self.ww.rc.minX ) / self.ww.rc.resolution );
	
	self.ww.rc.nodeLeft = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.rcNodesLR#left") );
	self.ww.rc.nodeRight = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.rcNodesLR#right") );
	self.ww.rc.maxY = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesLR#maxY");
	self.ww.rc.minY = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesLR#minY");
	self.ww.rc.maxDistH = getXMLFloat(xmlFile, "vehicle.woodWire.rcNodesLR#maxDist");
	self.ww.rc.resolution = 0.05;
	self.ww.rc.countH = math.ceil( (self.ww.rc.maxY - self.ww.rc.minY ) / self.ww.rc.resolution );	

	self.ww.loop1 = {};
	self.ww.loop1.node = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.loops#index1") );
	local x,y,z = getTranslation(self.ww.loop1.node);
	self.ww.loop1.pos = {x=x, y=y, z=z};
	self.ww.loop1.parent = getParent(self.ww.loop1.node);
	self.ww.loop1.trunk = nil;
	self.ww.loop1.trunkPositions = {};

	self.ww.loop2 = {};
	self.ww.loop2.node = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.loops#index2") );
	local x,y,z = getTranslation(self.ww.loop2.node);
	self.ww.loop2.pos = {x=x, y=y, z=z};
	self.ww.loop2.parent = getParent(self.ww.loop2.node);
	self.ww.loop2.trunk = nil;
	self.ww.loop2.trunkPositions = {};

	self.ww.conn1 = {};
	self.ww.conn1.node = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.connectionRopes#index1") );
	self.ww.conn1.target = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.connectionRopes#loopTarget1") ); 
	self.ww.conn1.a = false;
	
	self.ww.conn2 = {};
	self.ww.conn2.node = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.connectionRopes#index2") );
	self.ww.conn2.target = Utils.indexToObject( self.components, getXMLString(xmlFile, "vehicle.woodWire.connectionRopes#loopTarget2") ); 
	self.ww.conn2.a = false;

	self.ww.mt4joints = {};
	self.ww.mt4joints.index = Utils.indexToObject( self.components, getXMLString( xmlFile, "vehicle.woodWire.movingToolToCheckForJointUpdate#index" ) );
	self.ww.mt4joints.lastRot = { getRotation(self.ww.mt4joints.index) };	
end;

function WoodWire:delete()
	if self.ww.playerTrigger ~= nil then
		removeTrigger(self.ww.playerTrigger);
	end;

	if self.ww.trunkTrigger ~= nil then
		removeTrigger(self.ww.trunkTrigger);
	end;	
end;

function WoodWire:readStream(streamId, connection)
	local loopToTrunk = streamReadBool(streamId);
	if loopToTrunk then
		self.ww.loadLoopAtTrunk1 = {};
		self.ww.loadLoopAtTrunk1.nId = streamReadInt32(streamId);
		self.ww.loadLoopAtTrunk1.pos = {};
		for i=1,4 do
			local e = {};
			for j=1,3 do
				table.insert( e, streamReadFloat32(streamId) );
			end;
			table.insert(self.ww.loadLoopatTrunk1.pos, e);
		end;
	end;
	local loopToTrunk = streamReadBool(streamId);
	if loopToTrunk then
		self.ww.loadLoopAtTrunk2 = {};
		self.ww.loadLoopAtTrunk2.nId = streamReadInt32(streamId);
		self.ww.loadLoopAtTrunk2.pos = {};
		for i=1,4 do
			local e = {};
			for j=1,3 do
				table.insert( e, streamReadFloat32(streamId) );
			end;
			table.insert(self.ww.loadLoopAtTrunk2.pos, e);
		end;		
	end;	
end;

function WoodWire:writeStream(streamId, connection)	
	if self.ww.loop1.trunk ~= nil then 
		streamWriteBool(streamId, true);
		streamWriteInt32(streamId, networkGetObjectId(self.ww.loop1.trunk));
		for i=1,4 do
			local pos = self.ww.loop1.trunkPositions[i];
			for j=1,3 do
				streamWriteFloat32(streamId, pos[j]);
			end;	
		end;
	else
		streamWriteBool(streamId, false);
	end;
	if self.ww.loop2.trunk ~= nil then 
		streamWriteBool(streamId, true);
		streamWriteInt32(streamId, networkGetObjectId(self.ww.loop2.trunk));
		for i=1,4 do
			local pos = self.ww.loop2.trunkPositions[i];
			for j=1,3 do
				streamWriteFloat32(streamId, pos[j]);
			end;	
		end;
	else
		streamWriteBool(streamId, false);
	end;
end;

function WoodWire:mouseEvent(posX, posY, isDown, isUp, button)
end;

function WoodWire:keyEvent(unicode, sym, modifier, isDown)
end;

function WoodWire:update(dt)	
	if self.isClient then 
		if self.ww.playerInTrigger and g_currentGui == nil and g_currentMission.controlledVehicle == nil then		
			if InputBinding.hasEvent(InputBinding.WOODWIRE_ATTACH) then			
				self:attachTrunksWire();				
			elseif InputBinding.hasEvent(InputBinding.WOODWIRE_DETACH) then			
				self:detachTrunksWire();		
			end;
			
			self:addHelpButtonTextWithCorrectMouseButtons("WOODWIRE_ATTACH", InputBinding.WOODWIRE_ATTACH, "WOODWIRE_ATTACH");
			self:addHelpButtonTextWithCorrectMouseButtons("WOODWIRE_DETACH", InputBinding.WOODWIRE_DETACH, "WOODWIRE_DETACH");
		end;
		
		if self.ww.conn1.a then			
			local wx1,wy1,wz1 = localToWorld( self.ww.conn1.target, 0,-0.005,0 );
			local x1,y1,z1 = worldToLocal( self.ww.conn1.node, wx1,wy1,wz1 );			
			
			local wx2,wy2,wz2 = localToWorld( self.ww.conn1.target, 0,-0.35,0 );
			local x2,y2,z2 = worldToLocal( self.ww.conn1.node, wx2,wy2,wz2 );			
			
			setShaderParameter( self.ww.conn1.node, "cv2", x1,y1,z1,0, false );
			setShaderParameter( self.ww.conn1.node, "cv3", x2,y2,z2,0, false );			
		end;
		if self.ww.conn2.a then			
			local wx1,wy1,wz1 = localToWorld( self.ww.conn2.target, 0,-0.005,0 );
			local x1,y1,z1 = worldToLocal( self.ww.conn2.node, wx1,wy1,wz1 );			
			
			local wx2,wy2,wz2 = localToWorld( self.ww.conn2.target, 0,-0.35,0 );
			local x2,y2,z2 = worldToLocal( self.ww.conn2.node, wx2,wy2,wz2 );			
			
			setShaderParameter( self.ww.conn2.node, "cv2", x1,y1,z1,0, false );
			setShaderParameter( self.ww.conn2.node, "cv3", x2,y2,z2,0, false );			
		end;
	end;
end;

function WoodWire:updateTick(dt)
	if self.isServer then
		local rot = { getRotation(self.ww.mt4joints.index) };
		local dirty = false;
		for i=1,3 do	
			if rot[i] ~= self.ww.mt4joints.lastRot[i] then
				dirty = true;
				self.ww.mt4joints.lastRot[i] = rot[i];
			end;
		end;
		if dirty then
			local s = table.getn(self.ww.attachedTrunks);
			if s > 0 then
				if self.ww.joint1Attached then
					setJointFrame(self.ww.attachedTrunks[1].wireJointIndex, 0, self.ww.joint1Node);
				end;
				if self.ww.joint2Attached then
					setJointFrame(self.ww.attachedTrunks[2].wireJointIndex, 0, self.ww.joint2Node);
				end;
			end
		end;
	end;
	if self.isClient then
		if self.ww.loadLoopAtTrunk1 ~= nil then
			if self.ww.loadLoopAtTrunk1.nId ~= nil then
				local t = networkGetObject( self.ww.loadLoopAtTrunk1.nId );
				self:setLoopToTrunk(1, t, self.ww.loadLoopAtTrunk1.pos[1], self.ww.loadLoopAtTrunk1.pos[2], self.ww.loadLoopAtTrunk1.pos[3], self.ww.loadLoopAtTrunk1.pos[4]);
			end;
			self.ww.loadLoopAtTrunk1 = nil;
		end;
		if self.ww.loadLoopAtTrunk2 ~= nil then
			if self.ww.loadLoopAtTrunk2.nId ~= nil then
				local t = networkGetObject( self.ww.loadLoopAtTrunk2.nId );
				self:setLoopToTrunk(2, t, self.ww.loadLoopAtTrunk2.pos[1], self.ww.loadLoopAtTrunk2.pos[2], self.ww.loadLoopAtTrunk2.pos[3], self.ww.loadLoopAtTrunk2.pos[4]);
			end;
			self.ww.loadLoopAtTrunk2 = nil;
		end;		
	end;
end;

function WoodWire:draw()

end;

function WoodWire:triggerCallbackWire(triggerId, otherId, onEnter, onLeave, onStay)
	--# check for player
	if onEnter and g_currentMission.controlPlayer ~= nil and g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
		self.ww.playerInTrigger = true;
	elseif onLeave and (g_currentMission.player == nil or otherId == g_currentMission.player.rootNode)  then
		self.ww.playerInTrigger = false;
	end;
	
	--# check for trunks
	if g_currentMission.treeManager ~= nil then 
		if onEnter then
			local trunk = g_currentMission.treeManager.nodeIdToTrunk[otherId]; 
			if trunk ~= nil then
				if trunk.isTrunk ~= nil and trunk.isTrunk == true then	
					local insert = true;
					for i,t in pairs(self.ww.triggerTrunks) do
						if t == trunk then
							insert = false;
							break;
						end;
					end;
					if insert then
						trunk.woodWire = self;
						trunk.mass = getMass(otherId);
						table.insert( self.ww.triggerTrunks, trunk );
					end;				
				end;
			end;
		elseif onLeave then
			local trunk = g_currentMission.treeManager.nodeIdToTrunk[otherId]; 
			if trunk ~= nil then
				if trunk.isTrunk ~= nil and trunk.isTrunk == true then	
					for i,t in pairs(self.ww.triggerTrunks) do
						if t == trunk then
							trunk.woodWire = nil;
							table.remove(self.ww.triggerTrunks, i);
							break;
						end;
					end;
				end;
			end;
		end;
	end;
end;

function WoodWire:rcCallbackWire(objectId, x, y, z, distance, nx, ny, nz, subShapeIndex)	
	local trunk = g_currentMission.treeManager.nodeIdToTrunk[objectId]; 
	if trunk ~= nil then
		if trunk.isTrunk ~= nil and trunk.isTrunk == true then
			self.ww.tmpTrunk = trunk;
			self.ww.dist = distance;
			self.ww.position = {x=x, y=y, z=z};
		end;
	end;	
end;

-- used to raycast for a specific trunk
function WoodWire:rcCallbackWire2(objectId, x, y, z, distance, nx, ny, nz, subShapeIndex)	
	local trunk = g_currentMission.treeManager.nodeIdToTrunk[objectId]; 
	if trunk ~= nil then
		if trunk == self.ww.tmpTrunk then
			self.ww.tmpTrunk2 = trunk;
			self.ww.dist = distance;
			self.ww.position = {x=x, y=y, z=z};
		end;
	end;	
end;

function WoodWire:attachTrunksWire(noEventSend) 
	WoodWireAttachEvent.sendEvent(self, true, noEventSend);
	
	if self.isServer then
		if self.ww.joint1Attached and self.ww.joint2Attached then	
			return;
		end;
	
		local trunkToAttach = nil;
		local minDist = math.huge;
		local minIdx = nil;
		local id = 1;							-- left or right
		
		for i,trunk in pairs(self.ww.triggerTrunks) do
			if trunk.woodWire == self and trunk.wireJointIndex == nil then
				local alreadyAttached = false;
				for i,trunk2 in pairs(self.ww.attachedTrunks) do	
					if trunk2 == trunk then	
						alreadyAttached = true;
						break;
					end;
				end;				
				
				-- find closest trunk 
				if not alreadyAttached and trunk.visNode ~= nil then
					local vp = { getWorldTranslation(trunk.visNode) };
					local rp = { getWorldTranslation(trunk.visRefNode) };

					local p;
					local p1 = { getWorldTranslation(self.ww.joint1Node) };
					local p2 = { getWorldTranslation(self.ww.joint2Node) };
					local pm = { (p1[1]+p2[1])/2, (p1[2]+p2[2])/2, (p1[3]+p2[3])/2 };
					
					local vm = Utils.vector3Length( vp[1]-pm[1],vp[2]-pm[2],vp[3]-pm[3] );
					local rm = Utils.vector3Length( rp[1]-pm[1],rp[2]-pm[2],rp[3]-pm[3] );
					local isCloser = false;
					if vm < rm then
						if vm < minDist then
							minDist = vm;
							minIdx = i;
							isCloser = true;
						end;
					else
						if rm < minDist then
							minDist = rm;
							minIdx = i;
							isCloser = true;
						end;
					end;					
					
					if isCloser == true then
						if self.ww.joint1Attached then
							id = 2;
						elseif self.ww.joint2Attached then
							id = 1;
						else
							if vm < rm then
								local d1 = Utils.vector3Length( vp[1]-p1[1], vp[2]-p1[2], vp[3]-p1[3] );
								local d2 = Utils.vector3Length( vp[1]-p2[1], vp[2]-p2[2], vp[3]-p2[3] );
								if d1 < d2 then
									id = 1;
								else
									id = 2;
								end;
							else
								local d1 = Utils.vector3Length( rp[1]-p1[1],rp[2]-p1[2],rp[3]-p1[3] );
								local d2 = Utils.vector3Length( rp[1]-p2[1],rp[2]-p2[2],rp[3]-p2[3] );
								if d1 < d2 then
									id = 1;
								else
									id = 2;
								end;						
							end;
						end;
					end;
				end;
			end;
		end;	

		if minIdx ~= nil then
			trunkToAttach = self.ww.triggerTrunks[minIdx];
			
			if trunkToAttach ~= nil then
				if true then
					local trunk = trunkToAttach;
					
					--# do raycasts for finding tree/trunk and estimation of its radius (better: its bounding points)
					
					-- 	and therefore: rotate trigger (its childs are the rcNodes)
					local xt,yt,zt = localDirectionToWorld( trunk.nodeId, 0,0,1 );
					local x,y,z = worldDirectionToLocal( self.components[1].node, xt,yt,zt );
					local alpha = math.atan( z/x );
					setRotation( self.ww.trunkTrigger, 0,-alpha,0 );						
					
					local posUp = {0, 0, 0};
					local posDown = {0, 0, 0};
					local posLeft = {0, 0, 0};
					local posRight = {0, 0, 0};
					
					-- from upper
					local pos = {x=0, y=-100000, z=0};
					local dx,dy,dz = localDirectionToWorld( self.ww.rc.nodeUp, 0,-1,0);
					local mean = 0;
					local cnt = 0;
					for i=1,self.ww.rc.countV do
						local px,py,pz = localToWorld( self.ww.rc.nodeUp, self.ww.rc.minX+self.ww.rc.resolution*(i-1),0,0 );
						self.ww.tmpTrunk = nil;
						raycastAll( px,py,pz, dx,dy,dz, "rcCallbackWire", self.ww.rc.maxDistV, self );
						
						if self.ww.tmpTrunk ~= nil then
							if trunk == self.ww.tmpTrunk then
								local x,y,z = worldToLocal( self.ww.rc.nodeUp, self.ww.position.x, self.ww.position.y, self.ww.position.z );
								if y > pos.y then
									pos.x = x;
									pos.y = y;
									pos.z = z;
								end;
								mean = mean + x;
								cnt = cnt + 1;
							end;
						end;
					end;
					mean = mean / cnt;
					if pos.y ~= -100000 then
						local wx,wy,wz = localToWorld( self.ww.rc.nodeUp, mean, pos.y, pos.z );				
						local x,y,z = worldToLocal( trunk.nodeId, wx,wy,wz );			
						posUp = {x,y,z};
					end;
					
					-- from lower
					local pos = {x=0, y=100000, z=0};
					local dx,dy,dz = localDirectionToWorld( self.ww.rc.nodeDown, 0,1,0);
					local mean = 0;
					local cnt = 0;					
					for i=1,self.ww.rc.countV do
						local px,py,pz = localToWorld( self.ww.rc.nodeDown, self.ww.rc.minX+self.ww.rc.resolution*(i-1),0,0 );
						self.ww.tmpTrunk = nil;
						raycastAll( px,py,pz, dx,dy,dz, "rcCallbackWire", self.ww.rc.maxDistV, self );
						
						if self.ww.tmpTrunk ~= nil then
							if trunk == self.ww.tmpTrunk then
								local x,y,z = worldToLocal( self.ww.rc.nodeDown, self.ww.position.x, self.ww.position.y, self.ww.position.z );
								if y < pos.y then
									pos.x = x;
									pos.y = y;
									pos.z = z;
								end;
								mean = mean + x;
								cnt = cnt + 1;								
							end;
						end;
					end;
					mean = mean / cnt;					
					if pos.y ~= 100000 then
						local wx,wy,wz = localToWorld( self.ww.rc.nodeDown, mean, pos.y, pos.z );				
						local x,y,z  = worldToLocal( trunk.nodeId, wx,wy,wz );					
						posDown = {x,y,z};
					end;
					
					self.ww.tmpTrunk = trunk;
					
					-- from left
					local pos = {x=-100000, y=0, z=0};
					local dx,dy,dz = localDirectionToWorld( self.ww.rc.nodeLeft, -1,0,0);
					local mean = 0;
					local cnt = 0;					
					for i=1,self.ww.rc.countH do
						local px,py,pz = localToWorld( self.ww.rc.nodeLeft, 0,self.ww.rc.minY+self.ww.rc.resolution*(i-1),0 );
						self.ww.tmpTrunk2 = nil;
						raycastAll( px,py,pz, dx,dy,dz, "rcCallbackWire2", self.ww.rc.maxDistH, self );
						
						if self.ww.tmpTrunk2 ~= nil then
							if trunk == self.ww.tmpTrunk2 then
								local x,y,z = worldToLocal( self.ww.rc.nodeLeft, self.ww.position.x, self.ww.position.y, self.ww.position.z );
								if x > pos.x then
									pos.x = x;
									pos.y = y;
									pos.z = z;
								end;
								mean = mean + y;
								cnt = cnt + 1;									
							end;
						end;
					end;
					mean = mean / cnt;					
					if pos.x ~= -100000 then
						local wx,wy,wz = localToWorld( self.ww.rc.nodeLeft, pos.x,mean,pos.z ); 
						local x,y,z  = worldToLocal( trunk.nodeId, wx,wy,wz );					
						posLeft = {x,y,z};
					end;	
					
					-- from right
					local pos = {x=100000, y=0, z=0};
					local dx,dy,dz = localDirectionToWorld( self.ww.rc.nodeRight, 1,0,0);
					local mean = 0;
					local cnt = 0;						
					for i=1,self.ww.rc.countH do
						local px,py,pz = localToWorld( self.ww.rc.nodeRight, 0,self.ww.rc.minY+self.ww.rc.resolution*(i-1),0 );
						self.ww.tmpTrunk2 = nil;
						raycastAll( px,py,pz, dx,dy,dz, "rcCallbackWire2", self.ww.rc.maxDistH, self );
						
						if self.ww.tmpTrunk2 ~= nil then
							if trunk == self.ww.tmpTrunk2 then
								local x,y,z = worldToLocal( self.ww.rc.nodeRight, self.ww.position.x, self.ww.position.y, self.ww.position.z );
								if x < pos.x then
									pos.x = x;
									pos.y = y;
									pos.z = z;
								end;
								mean = mean + y;
								cnt = cnt + 1;										
							end;
						end;
					end;
					mean = mean / cnt;	
					if pos.x ~= 100000 then
						local wx,wy,wz = localToWorld( self.ww.rc.nodeRight,  pos.x,mean,pos.z ); --pos.x, pos.y, pos.z );
						local x,y,z  = worldToLocal( trunk.nodeId, wx,wy,wz );					
						posRight = {x,y,z};
					end;					
						
					if posUp[1] ~= 0 and posDown[1] ~= 0 and posLeft[1] ~= 0 and posRight[1] ~= 0 then
						local jointNode = self.ww.joint1Node;
						if id == 2 then
							jointNode = self.ww.joint2Node;
							self.ww.joint2Attached = true;
						else
							self.ww.joint1Attached = true;
						end;
						
						local constr = JointConstructor:new();
						constr:setActors(self.components[self.ww.trunkAttachCompIndex].node, trunk.nodeId);
						constr:setJointTransforms(jointNode, jointNode);
						for a=1, 3 do
							constr:setRotationLimit(a-1, -self.ww.trunkJointRotLimit[a], self.ww.trunkJointRotLimit[a]);
							constr:setTranslationLimit(a-1, true, -self.ww.trunkJointTransLimit[a], self.ww.trunkJointTransLimit[a]);
						end;
						trunk.wireJointIndex = constr:finalize();								
						table.insert(self.ww.attachedTrunks, trunk);					
					
						self:setLoopToTrunk( id, trunk, posUp, posDown, posLeft, posRight );
						g_server:broadcastEvent(WoodWireLoopAttachEvent:new( self, id, trunk, posUp, posDown, posLeft, posRight ), nil, nil, self); 
					end;

					setRotation( self.ww.trunkTrigger, 0,0,0 );
				end;
			end;					
		end;
	end;
end;


function WoodWire:detachTrunksWire(noEventSend)
	WoodWireAttachEvent.sendEvent(self, false, noEventSend);
	
	if self.isServer then
		while table.getn(self.ww.attachedTrunks) > 0 do
			for i,trunk in pairs(self.ww.attachedTrunks) do
				removeJoint(trunk.wireJointIndex);
				trunk.wireJointIndex = nil;
				table.remove( self.ww.attachedTrunks, i );
			end;
		end;
		
		self.ww.joint1Attached = false;
		self.ww.joint2Attached = false;		
		
		self:removeLoopFromTrunk();
		g_server:broadcastEvent(WoodWireLoopDetachEvent:new(self), nil, nil, self); 
	end;
end;


function WoodWire:setLoopToTrunk(id, trunkId, posU, posD, posL, posR)
	local vec = {posU[1]-posD[1], posU[2]-posD[2], posU[3]-posD[3]};
	local dia = Utils.vector3Length(vec[1], vec[2], vec[3]);

	local loop = self.ww.loop1;
	local joint = self.ww.joint1Node;
	if id == 2 then
		loop = self.ww.loop2;
		joint = self.ww.joint2Node;
		self.ww.conn2.a = true;
		-- data for MP sync
		self.ww.loop2.trunk = trunkId;
		table.insert( self.ww.loop2.trunkPositions, {posU[1], posU[2], posU[3]} );
		table.insert( self.ww.loop2.trunkPositions, {posD[1], posD[2], posD[3]} );
		table.insert( self.ww.loop2.trunkPositions, {posL[1], posL[2], posL[3]} );
		table.insert( self.ww.loop2.trunkPositions, {posR[1], posR[2], posR[3]} );		
		setVisibility( self.ww.conn2.node, true );
	else	
		self.ww.conn1.a = true;
		-- data for MP sync
		self.ww.loop1.trunk = trunkId;
		table.insert( self.ww.loop1.trunkPositions, {posU[1], posU[2], posU[3]} );
		table.insert( self.ww.loop1.trunkPositions, {posD[1], posD[2], posD[3]} );
		table.insert( self.ww.loop1.trunkPositions, {posL[1], posL[2], posL[3]} );
		table.insert( self.ww.loop1.trunkPositions, {posR[1], posR[2], posR[3]} );
		setVisibility( self.ww.conn1.node, true );
	end;
	link( trunkId.nodeId, loop.node );

	-- offset from trunk?
	local vx,vy,vz = worldDirectionToLocal(trunkId.nodeId, 0,dia/8,0);  
	setTranslation( loop.node, posU[1]+vx, posU[2]+vy, posU[3]+vz );
	
	setDirection( loop.node, 0,-1,0, posU[1]-posD[1],posU[2]-posD[2],posU[3]-posD[3] );

	setVisibility( getChildAt(loop.node, 0), false );
	local sMesh = getChildAt(loop.node, 1);
	setVisibility( sMesh, true );
	
	setTranslation( sMesh, 0,0,0 );	
	setDirection( sMesh, 0,-1,0, 0,0,1 );
	
	--
	local wx1,wy1,wz1 = localToWorld( trunkId.nodeId, posL[1], posL[2], posL[3] );
	local x1,y1,z1 = worldToLocal( sMesh, wx1,wy1,wz1 );
	
	local wx2,wy2,wz2 = localToWorld( trunkId.nodeId, posD[1], posD[2], posD[3] );
	local x2,y2,z2 = worldToLocal( sMesh, wx2,wy2,wz2 );
	
	local wx3,wy3,wz3 = localToWorld( trunkId.nodeId, posR[1], posR[2], posR[3] );
	local x3,y3,z3 = worldToLocal( sMesh, wx3,wy3,wz3 );	
	
	-- nice circle
	setShaderParameter( sMesh, "cv0", x3, 	y3,	z3 ,	0, false );
	setShaderParameter( sMesh, "cv1", 0, 		0,	0,	0, false );
	setShaderParameter( sMesh, "cv2", x1*1.1,	y1,	z1,			0, false );
	setShaderParameter( sMesh, "cv3", x2, 		y2,	z2*1.1, 	0, false );
	setShaderParameter( sMesh, "cv4", x3*1.1, 	y3,	z3, 		0, false );
	setShaderParameter( sMesh, "cv5", 0, 		0, 	0,	0, false );
	setShaderParameter( sMesh, "cv6", x1,	y1,	z1, 	0, false );	
end;

function WoodWire:removeLoopFromTrunk()
	local loop = self.ww.loop1;
	link( loop.parent, loop.node );
	setTranslation( loop.node, loop.pos.x, loop.pos.y, loop.pos.z );
	setRotation( loop.node, 0,0,0 );
	setVisibility( getChildAt(loop.node, 0), true );
	setVisibility( getChildAt(loop.node, 1), false );
	
	loop = self.ww.loop2;
	link( loop.parent, loop.node );
	setTranslation( loop.node, loop.pos.x, loop.pos.y, loop.pos.z );
	setRotation( loop.node, 0,0,0 );
	setVisibility( getChildAt(loop.node, 0), true );
	setVisibility( getChildAt(loop.node, 1), false );
	
	self.ww.conn1.a = false;
	self.ww.conn2.a = false;
	
	self.ww.loop1.trunk = nil;
	self.ww.loop1.trunkPositions = {};
	self.ww.loop2.trunk = nil;
	self.ww.loop2.trunkPositions = {};
	
	setVisibility( self.ww.conn1.node, false );
	setVisibility( self.ww.conn2.node, false );
end;


function WoodWire:addHelpButtonTextWithCorrectMouseButtons(inputBindingName, inputBinding, text)
	local inputBindingActionIndex = InputBinding[inputBindingName];
	local isMouseButton = false;
	local buttonName = nil;
	
	if inputBindingActionIndex ~= nil then
		if InputBinding.actions[inputBindingActionIndex] ~= nil then
			local buttonIndex = InputBinding.actions[inputBindingActionIndex].mouseButtons[1];
			if buttonIndex ~= nil then
				if buttonIndex == 1 then
					isMouseButton = true;
					buttonName = g_i18n:getText("leftMouseButton");
				end;
				if buttonIndex == 2 then
					isMouseButton = true;
					buttonName = g_i18n:getText("middleMouseButton");
				end;
				if buttonIndex == 3 then
					isMouseButton = true;
					buttonName = g_i18n:getText("rightMouseButton");
				end;
			end;
		end;
	end;
	
	if isMouseButton then
		g_currentMission:addExtraPrintText(buttonName..": "..g_i18n:getText(text));
	else
		g_currentMission:addHelpButtonText(g_i18n:getText(text), inputBinding);
	end;
end;	