----------------------------------------------------------------------------
-- @Author: ViperGTS96------------------------------------------------------
----------------------------------------------------------------------------
--------------------"The simplest design is the best design." --------------
----------------------------------------------------------------------------
----------------------------------------------------------------------------
mudSystem_physics = {};
mudSystem_physics.brakeFactor = 10.0;
mudSystem_physics.massMultipler = 25.0;

function mudSystem_physics:updateWheelSink(superFunc, wheel, dt, groundWetness, force)
	if (wheel.supportsWheelSink or (self.spec_mudSystem ~= nil and self.spec_mudSystem.enableMudSystem)) and self.isServer and self.isAddedToPhysics then --<
		local spec = self.spec_wheels;
		local maxSink = wheel.maxWheelSink;
		local sinkTarget = wheel.sinkTarget;
		local lastSpeed = self:getLastSpeed();
		if force ~= nil then  --<
			force = true; 
			lastSpeed = math.max(3,lastSpeed);
		else
			force = false; 
		end; 
		local interpolationFactor = 1;
		if (wheel.contact ~= Wheels.WHEEL_NO_CONTACT and lastSpeed > 0.3) or force then
			local x, _, z = getWorldTranslation(wheel.repr);
			local noiseValue = 0;
			if wheel.densityType > 0 then
				local xPerlin = math.floor(x * 100) * 0.01;
				local zPerlin = math.floor(z * 100) * 0.01;
				local perlinNoise = Wheels.perlinNoiseSink;
				local noiseSink = 0.5 * (1 + getPerlinNoise2D(xPerlin * perlinNoise.randomFrequency, zPerlin * perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed));
				perlinNoise = Wheels.perlinNoiseWobble;
				local noiseWobble = 0.5 * (1 + getPerlinNoise2D(xPerlin * perlinNoise.randomFrequency, zPerlin * perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed));
				local gravity = 9.81;
				local tireLoad = getWheelShapeContactForce(wheel.node, wheel.wheelShape);
				if tireLoad ~= nil then
					local nx, ny, nz = getWheelShapeContactNormal(wheel.node, wheel.wheelShape);
					local dx, dy, dz = localDirectionToWorld(wheel.node, 0, -1, 0);
					tireLoad = -tireLoad * MathUtil.dotProduct(dx, dy, dz, nx, ny, nz);
					tireLoad = tireLoad + math.max(ny * gravity, 0) * wheel.mass;
				else
					tireLoad = 0;
				end;
				tireLoad = tireLoad / gravity;
				local loadFactor = math.min(1, math.max(0, tireLoad / wheel.maxLatStiffnessLoad));
				noiseSink = 0.333 * (2 * loadFactor + groundWetness) * noiseSink;
				noiseValue = math.max(noiseSink, noiseWobble);
			end;
			------------------------------------------------------------------------------------------Start
			if wheel.isInMud == nil or not wheel.isInMud then
				maxSink = Wheels.MAX_SINK[wheel.densityType] or maxSink;
			else
				local minRadius = wheel.radius;
				local maxRadius = math.max(0.425,wheel.radiusOriginal*0.8);
				local mudSinkFactor = math.random(minRadius, maxRadius);
				if (wheel.isInFieldMud ~= nil and wheel.isInFieldMud) then 
					local fieldDepth = g_currentMission.mudFieldSinkUpdater.fieldSinkAmount;
					mudSinkFactor = mudSinkFactor*fieldDepth; 
				end;
				if wheel.isCareWheel == nil or not wheel.isCareWheel then
					local extraWheelCount = 0;
					if wheel.additionWheels ~= nil then extraWheelCount = #wheel.additionWheels; end;
					mudSinkFactor = mudSinkFactor - ((wheel.width*0.25)*extraWheelCount);
					if wheel.mudSystem_isCrawler ~= nil and wheel.mudSystem_isCrawler then mudSinkFactor = mudSinkFactor/2; end;
					if mudSinkFactor <= 0 then mudSinkFactor = 0.05; end;
				end;
				maxSink = mudSinkFactor;
			end;
			local notInMud = (wheel.isInMud == nil or not wheel.isInMud) and (wheel.isInFieldMud == nil or not wheel.isInFieldMud);
			if notInMud then
			------------------------------------------------------------------------------------------End
				if wheel.densityType == FieldGroundType.PLOWED and wheel.oppositeWheelIndex ~= nil then
					local oppositeWheel = spec.wheels[wheel.oppositeWheelIndex];
					if oppositeWheel.densityType ~= nil and oppositeWheel.densityType ~= FieldGroundType.PLOWED then
						maxSink = maxSink * 1.3;
					end;
				end;
				sinkTarget = math.min(0.2 * wheel.radiusOriginal, math.min(maxSink, wheel.maxWheelSink) * noiseValue);
			------------------------------------------------------------------------------------------Start
			else
				if maxSink > wheel.radiusOriginal*0.8 then maxSink = wheel.radiusOriginal*0.8; end;
				if sinkTarget < maxSink then
					sinkTarget = sinkTarget + (0.225* (dt / 1000));
				else
					sinkTarget = maxSink;
				end;
			end;
			------------------------------------------------------------------------------------------End
		elseif wheel.contact == Wheels.WHEEL_NO_CONTACT then
			sinkTarget = 0;
			lastSpeed = 10;
			interpolationFactor = 0.075;
		end;

		if wheel.sinkTarget < sinkTarget then
			wheel.sinkTarget = math.min(sinkTarget, wheel.sinkTarget + 0.05 * math.min(30, math.max(0, lastSpeed - 0.2)) * dt / 1000 * interpolationFactor);
		elseif sinkTarget < wheel.sinkTarget then
			wheel.sinkTarget = math.max(sinkTarget, wheel.sinkTarget - 0.05 * math.min(30, math.max(0, lastSpeed - 0.2)) * dt / 1000 * interpolationFactor);
		end;

		if math.abs(wheel.sink - wheel.sinkTarget) > 0.001 then
			wheel.sink = wheel.sinkTarget;
			local radius = wheel.radiusOriginal - wheel.sink;
			if radius ~= wheel.radius then
				wheel.radius = radius;
				if self.isServer then
					self:setWheelPositionDirty(wheel);
					local sinkFactor = wheel.sink / maxSink * (1 + 0.4 * groundWetness);
					wheel.sinkLongStiffnessFactor = 1 - 0.1 * sinkFactor;
					wheel.sinkLatStiffnessFactor = 1 - 0.2 * sinkFactor;
					self:setWheelTireFrictionDirty(wheel);
				end;
			end;
		end;
	end;
end;


function mudSystem_physics:updateTireTrackNode(superFunc, tireTrackNode, allowTireTracks, groundWetness)
	local wheel = tireTrackNode.wheel;
	if not allowTireTracks then
		self.tireTrackSystem:cutTrack(tireTrackNode.tireTrackIndex);
		self.tireTrackSystem:cutTrack(tireTrackNode.mudTrackIndex);
		if self.mudSystemMudTrackIndex ~= nil then self.tireTrackSystem:cutTrack(self.mudSystemMudTrackIndex); end;
		return;
	end;

	if tireTrackNode.activeFunc ~= nil and not tireTrackNode.activeFunc() then
		self.tireTrackSystem:cutTrack(tireTrackNode.tireTrackIndex);
		self.tireTrackSystem:cutTrack(tireTrackNode.mudTrackIndex);
		if self.mudSystemMudTrackIndex ~= nil then self.tireTrackSystem:cutTrack(self.mudSystemMudTrackIndex); end;
		return;
	end;

	local wx, wy, wz = nil;
	if not tireTrackNode.isAdditionalTrack then
		local netInfo = wheel.netInfo;
		wz = netInfo.z;
		wy = netInfo.y;
		wx = netInfo.x;
	else
		wx, wy, wz = worldToLocal(tireTrackNode.parent, getWorldTranslation(tireTrackNode.linkNode));
	end;

	wy = wy - tireTrackNode.radius;
	wx = wx + tireTrackNode.xOffset;
	wx, wy, wz = localToWorld(tireTrackNode.parent, wx, wy, wz);
	wy = math.max(wy, getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz));
	local r, g, b, a, _ = self:getTireTrackColor(wheel, wx, wy, wz, groundWetness);

	if r ~= nil then
		local ux, uy, uz = localDirectionToWorld(wheel.node, -wheel.directionX, -wheel.directionY, -wheel.directionZ);
		local tireDirection = self.movingDirection;
		if tireTrackNode.inverted then
			tireDirection = tireDirection * -1;
		end;
		-------------------------------------------------------------------------------
		if tireTrackNode.mudTrackIndex ~= nil and wheel.isInFieldMud ~= nil and wheel.isInFieldMud then
			self.tireTrackSystem:addTrackPoint(tireTrackNode.mudTrackIndex, wx, wy, wz, ux, uy, uz, r, g, b, wheel.dirtAmount, a, tireDirection);
			if wheel == self.spec_wheels.wheels[1] then mudSystem:createBodyTrack(self, wheel.mudSystemtrackIndex, r,g,b,a,ux,uy,uz, wheel.dirtAmount, tireDirection); end;
		else
			self.tireTrackSystem:addTrackPoint(tireTrackNode.tireTrackIndex, wx, wy, wz, ux, uy, uz, r, g, b, wheel.dirtAmount, a, tireDirection);
			if self.mudSystemMudTrackIndex ~= nil then self.tireTrackSystem:cutTrack(self.mudSystemMudTrackIndex); end;
			self.tireTrackSystem:cutTrack(tireTrackNode.mudTrackIndex);
		end;
		-------------------------------------------------------------------------------
	else
		self.tireTrackSystem:cutTrack(tireTrackNode.tireTrackIndex);
		if self.mudSystemMudTrackIndex ~= nil then self.tireTrackSystem:cutTrack(self.mudSystemMudTrackIndex); end;
		self.tireTrackSystem:cutTrack(tireTrackNode.mudTrackIndex);
	end;
end;

function mudSystem_physics:addTireTrackNode(superFunc, wheel, isAdditionalTrack, parent, linkNode, tireTrackAtlasIndex, width, radius, xOffset, inverted, activeFunc)
	local spec = self.spec_wheels
	local tireTrackNode = {
		wheel = wheel,
		isAdditionalTrack = isAdditionalTrack,
		parent = parent,
		linkNode = linkNode,
		tireTrackAtlasIndex = tireTrackAtlasIndex,
		width = width,
		radius = radius,
		xOffset = xOffset,
		inverted = inverted,
		activeFunc = activeFunc
	}

	if self.tireTrackSystem ~= nil then
		tireTrackNode.tireTrackIndex = self.tireTrackSystem:createTrack(width, tireTrackAtlasIndex)
		tireTrackNode.mudTrackIndex = self.tireTrackSystem:createTrack(wheel.width*1.75, 4);

		if tireTrackNode.tireTrackIndex ~= nil then
			table.insert(spec.tireTrackNodes, tireTrackNode)

			return #spec.tireTrackNodes
		end
	end
end

function mudSystem_physics:updateWheelFriction(superFunc, wheel, dt, groundWetness)
	if self.isServer then
		local isOnField = wheel.densityType ~= 0;
		local depth = wheel.lastColor[4];
		local snowScale = 0;

		local isInMud = ((wheel.isInMud ~= nil and wheel.isInMud) or (wheel.isInFieldMud ~= nil and wheel.isInFieldMud));
		local sunkAmount = math.max(0.001,g_currentMission.mudFieldSinkUpdater.fieldSinkAmount);
		if wheel.isInFieldMud ~= nil and not wheel.isInFieldMud then sunkAmount = 1; end;

		if isInMud then
			if self.getMotor ~= nil then
				if self:getMotor().lastAcceleratorPedal ~= 0 then 
					groundWetness = sunkAmount;
					snowScale = sunkAmount;
				end;
			end;
			isOnField = true;
		elseif wheel.hasSnowContact then
			groundWetness = 0;
			snowScale = 1;
		end;

		local groundType = WheelsUtil.getGroundType(isOnField, wheel.contact ~= Wheels.WHEEL_GROUND_CONTACT, depth);
		local coeff = WheelsUtil.getTireFriction(wheel.tireType, groundType, groundWetness, snowScale);
		
		local brakeBypass = false;
		if isInMud then
			if self.getMotor ~= nil then
				if self:getMotor().lastAcceleratorPedal ~= 0 then 
					local modifier = 0.25;
					if (wheel.additionalWheels ~= nil) or (wheel.tireType == WheelsUtil.getTireType("crawler")) or self.spec_mudSystem.hasFlotationTires then
						modifier = 0.5;
					elseif wheel.tireType ~= WheelsUtil.getTireType("mud") then
						modifier = 0.125;
					end;
					coeff = math.max(coeff*modifier,0.05);
					brakeBypass = true;
				end;
			else
				brakeBypass = true;
			end;
		end;

		if self:getLastSpeed() > 0.2 and coeff ~= wheel.tireGroundFrictionCoeff then
			wheel.tireGroundFrictionCoeff = coeff;
			self:setWheelTireFrictionDirty(wheel);
		end;

		self:updateMudResistance(wheel, isInMud and brakeBypass, sunkAmount, dt);
	end;
end;

function mudSystem_physics:updateMudResistance(wheel, isInMud, sunkAmount, dt)
	if isInMud then
		local isAttachment = false;
		local dividend = 1;
		if self.spec_attacherJoints ~= nil then
			dividend = 1+#self.spec_attacherJoints.attachedImplements;
		end;
		if self.spec_attachable ~= nil and self.spec_attachable.attacherVehicle ~= nil then
			if self ~= self.spec_attachable.attacherVehicle:getRootVehicle() then
				isAttachment = true;
			end;
		end;
		local multiplier = mudSystem_physics.brakeFactor*wheel.brakeFactor;
		multiplier = multiplier/dividend;
		local brakeFactor = mudSystem.mudDensity*(multiplier-((wheel.width*2)*0.1));
		if not isAttachment and wheel.width < 1.0 then 
			brakeFactor = brakeFactor * (1+(1-wheel.width)); 
		end;
		if brakeFactor < 0.1 then brakeFactor = 0.1 end;
		if wheel.isCareWheel then
			brakeFactor = brakeFactor*1.25;
		end;
		local additionalWheelsModifier = 1;
		if wheel.additionalWheels ~= nil then
			additionalWheelsModifier = #wheel.additionalWheels+1
			brakeFactor = brakeFactor/additionalWheelsModifier;
		end;
		if isAttachment then brakeFactor = brakeFactor/2; end;
		if wheel.isInFieldMud then brakeFactor = brakeFactor * sunkAmount; end;

		if brakeFactor > 0  and not self.spec_mudSystem.hasFlotationTires then 
			WheelsUtil.updateWheelPhysics(self, wheel, brakeFactor, dt);
		end;

		if wheel.mass ~= nil then
			if not isAttachment and wheel.mudlessMass == nil then
				wheel.mudlessMass = wheel.mass;
				local typeModifier = 1;
				if (additionalWheelsModifier > 1) or (wheel.tireType == WheelsUtil.getTireType("crawler")) then
					typeModifier = 0.5
				elseif wheel.tireType ~= WheelsUtil.getTireType("mud") then
					typeModifier = 2.25;
				end;
				wheel.mass = wheel.mass + ((typeModifier*mudSystem_physics.massMultipler)*sunkAmount);
				if wheel.fix_ForcePointRatio ~= nil then
					wheel.forcePointRatio = 0.45;
				end;
			end;
		end;
	else
		if wheel.mass ~= nil and wheel.mudlessMass ~= nil then
			wheel.mass = wheel.mudlessMass;
			wheel.mudlessMass = nill;
			if wheel.fix_ForcePointRatio ~= nil then
				wheel.forcePointRatio = wheel.fix_ForcePointRatio;
			end;
		end;
	end;
end;

function mudSystem_physics:getIsSupportAnimationAllowed(superFunc, supportAnimation)
	if self.spec_mudSystem ~= nil then
		if self.spec_wheels ~= nil then
			if self.spec_wheels.wheels ~= nil then
				for _,wheel in pairs(self.spec_wheels.wheels) do
					if wheel.isInMud ~= nil and wheel.isInMud then
						return self:getAnimationTime(supportAnimation.animationName) > 0;
					end; 
				end;
			end;
		end;
	end;
	return self.playAnimation ~= nil;
end;

function mudSystem_physics:onAttachableLoadFinished(savegame)
	if self.spec_attachable ~= nil then
		local spec = self.spec_attachable;
		if spec.supportAnimations ~= nil then
			for _, supportAnimation in ipairs(spec.supportAnimations) do
				if self.spec_mudSystem ~= nil then
					if self.spec_wheels ~= nil then
						if self.spec_wheels.wheels ~= nil then
							for _,wheel in pairs(self.spec_wheels.wheels) do
								if wheel.isInMud ~= nil and wheel.isInMud then
									self:playAnimation(supportAnimation.animationName, -1, nil, true, false);
									AnimatedVehicle.updateAnimationByName(self, supportAnimation.animationName, 9999999, true);
									break;
								end; 
							end;
						end;
					end;
				end;
			end;
		end;
	end;
end;

function mudSystem_physics:updateWheelChockPosition(superFunc, wheelChock, isInParkingPosition)
	if wheelChock.wheel.isInMud ~= nil and wheelChock.wheel.isInMud then
		return;
	else 
		superFunc(self, wheelChock, isInParkingPosition);
	end;
end;

Wheels.updateWheelSink = Utils.overwrittenFunction(Wheels.updateWheelSink, mudSystem_physics.updateWheelSink);
Wheels.updateTireTrackNode = Utils.overwrittenFunction(Wheels.updateTireTrackNode, mudSystem_physics.updateTireTrackNode);
Wheels.updateWheelFriction = Utils.overwrittenFunction(Wheels.updateWheelFriction, mudSystem_physics.updateWheelFriction);
Wheels.addTireTrackNode = Utils.overwrittenFunction(Wheels.addTireTrackNode, mudSystem_physics.addTireTrackNode);