ok enhance my code by spawning PBF particles from the research paper position based fluids. There is a if statement that already tells you when to spawn them but i think that needs more precesion. Also I want the particles to work perfectly in unison with the pre existing simulation and generate and delete when it is supposed to. Finally dont forget to blacklist the particles in the raycast. here is the current code Script: -- MainSimulation Script

local GMFluxContainer = require(game.ServerScriptService.GMFluxContainer)
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local CollectionService = game:GetService("CollectionService")
local sphParticleFolder=game.Workspace:WaitForChild("SPHParticles")
-- Simulation parameters
local texSize = 128 -- Adjust as needed (reasonable sizes: 64, 128)
local cellSize = 2 -- Size of each cell in world units
local simPosition = Vector3.new(-100, 0, -200) -- Adjust to move the fluid grid

-- Controllable simulation parameters
local simulationParameters = {
	fluxStrength = 0.25,       -- Controls the fluid flow strength
	fluxAtten = 0.975,         -- Fluid attenuation
	fluxSpill = 0.0,           -- Fluid spill threshold
	simulationSpeed = 0.03,    -- Controls the simulation step interval
	fluidGenerationRate = 100, -- Controls the rate at which fluid is added per source per step
	heightmapUpdateInterval = 1 -- Interval in seconds to update the heightmap
}

-- Create an instance of the simulation container
local sim = GMFluxContainer.new(texSize, simPosition, cellSize, simulationParameters)

-- Visualization parts parent
local visualizationFolder = Instance.new("Folder")
visualizationFolder.Name = "VisualizationParts"
visualizationFolder.Parent = Workspace

-- Create parts to visualize the simulation
local partsGrid = {}
local basePosition = sim.position

for x = 1, sim.texSize do
	partsGrid[x] = {}
	local worldX = basePosition.X + (x - 1) * sim.cellSize
	for y = 1, sim.texSize do
		local part = Instance.new("Part")
		part.Size = Vector3.new(sim.cellSize, 0.1, sim.cellSize) -- Initial minimal size
		local height = sim.cells[x][y][GMFluxContainer.FluxCell.Height]
		part.Position = Vector3.new(worldX, height + 0.05, basePosition.Z + (y - 1) * sim.cellSize)
		part.Anchored = true
		part.Material = Enum.Material.Slate -- Terrain material
		part.Color = Color3.fromRGB(139, 69, 19) -- Brown color for terrain
		part.Transparency = 0
		part.CastShadow = false
		part.Parent = visualizationFolder -- Parent to visualization folder

		-- Add attribute to track particle emitter
		part:SetAttribute("HasParticleEmitter", false)
		partsGrid[x][y] = part
	end
end

-- List of collidable objects (Advanced collisions)
local collidableObjectsFolder = Workspace:FindFirstChild("CollidableObjects")
local collidableObjects = {}
if collidableObjectsFolder then
	collidableObjects = collidableObjectsFolder:GetChildren()
end

-- List of water sources
local waterSources = {
	{x = math.floor(sim.texSize / 2), y = math.floor(sim.texSize / 2)},
	-- Add more sources as needed
}

-- Function to generate the terrain heightmap using raycasts
local function generateTerrainHeightmap(sim)
	local heightmap = {}
	local basePosition = sim.position
	local cellSize = sim.cellSize

	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = {visualizationFolder}
	raycastParams.IgnoreWater = false

	local maxTerrainHeight = 1000 -- Adjust based on your terrain
	local direction = Vector3.new(0, -maxTerrainHeight - 100, 0)

	for x = 1, sim.texSize do
		heightmap[x] = {}
		local worldX = basePosition.X + (x - 0.5) * cellSize
		for y = 1, sim.texSize do
			local worldZ = basePosition.Z + (y - 0.5) * cellSize
			local origin = Vector3.new(worldX, maxTerrainHeight, worldZ)
			local raycastResult = Workspace:Raycast(origin, direction, raycastParams)
			local height = 0

			if raycastResult then
				height = raycastResult.Position.Y
			else
				-- No hit, set to base level
				height = 0
			end

			heightmap[x][y] = height
		end
	end

	return heightmap
end

-- Generate the initial terrain heightmap
local heightmap = generateTerrainHeightmap(sim)

-- Set the terrain heightmap in the simulation
sim:cellEncodeHeightmap(heightmap, 1) -- Height factor is 1

-- Simulation loop
local lastHeightmapUpdate = 0
local simulationTimer = 0

RunService.Heartbeat:Connect(function(deltaTime)
	simulationTimer = simulationTimer + deltaTime

	-- Add fluid at specified water sources
	for _, source in ipairs(waterSources) do
		sim:addFluid(source.x, source.y, simulationParameters.fluidGenerationRate)
	end

	-- Perform simulation steps at the specified interval
	if simulationTimer >= simulationParameters.simulationSpeed then
		simulationTimer = 0

		-- Perform simulation steps
		sim:stepFlux()
		sim:stepDepth()

		-- Handle collisions
		sim:handleCollisions(collidableObjects)

		-- Update visualization
		for x = 1, sim.texSize do
			local partsColumn = partsGrid[x]
			local simCellsColumn = sim.cells[x]
			local worldX = basePosition.X + (x - 1) * sim.cellSize
			for y = 1, sim.texSize do
				local part = partsColumn[y]
				local cell = simCellsColumn[y]
				local depth = cell[GMFluxContainer.FluxCell.Depth]
				local height = cell[GMFluxContainer.FluxCell.Height]
				local worldZ = basePosition.Z + (y - 1) * sim.cellSize

				if depth > 0 then
					-- Scale the part to represent the fluid depth
					part.Size = Vector3.new(sim.cellSize, depth, sim.cellSize)
					part.Position = Vector3.new(worldX, height + depth / 2, worldZ)
					-- Set material and color for water
					part.Material = Enum.Material.Water
					-- Color intensity based on depth
					local colorIntensity = math.clamp(depth / 10, 0, 1)
					part.Color = Color3.new(0, 0, colorIntensity)
					part.Transparency = 0.3 -- Less transparent with more depth
				else
					-- Minimal size for empty cells
					part.Size = Vector3.new(sim.cellSize, 0.1, sim.cellSize)
					part.Position = Vector3.new(worldX, height + 0.05, worldZ)
					-- Set material and color for terrain
					part.Material = Enum.Material.Slate -- Terrain material
					part.Color = Color3.fromRGB(139, 69, 19) -- Brown color for terrain
					part.Transparency = 0
				end


				-- Detect high flux (too much force)
				-- Compute total flux magnitude for the cell
				local fluxLD = cell[GMFluxContainer.FluxCell.FluxLD]
				local fluxRU = cell[GMFluxContainer.FluxCell.FluxRU]
				local totalFlux = math.abs(fluxLD.left) + math.abs(fluxLD.down) + math.abs(fluxRU.right) + math.abs(fluxRU.up)

				-- Set a flux threshold for creating particles
				local fluxThreshold = 15 -- Adjust this value as needed

				-- Detect cliffs
				-- Compare current cell height with neighboring cells
				local isCliff = false
				local heightDifferenceThreshold = 5 -- Adjust based on terrain height variation
				local neighbors = {}

				if x > 1 then
					table.insert(neighbors, sim.cells[x - 1][y])
				end
				if x < sim.texSize then
					table.insert(neighbors, sim.cells[x + 1][y])
				end
				if y > 1 then
					table.insert(neighbors, sim.cells[x][y - 1])
				end
				if y < sim.texSize then
					table.insert(neighbors, sim.cells[x][y + 1])
				end

				for _, neighborCell in ipairs(neighbors) do
					local neighborHeight = neighborCell[GMFluxContainer.FluxCell.Height]
					if math.abs(height - neighborHeight) >= heightDifferenceThreshold then
						isCliff = true
						break
					end
				end

				-- If total flux exceeds threshold or is a cliff, spawn PBF particles
				if totalFlux >= fluxThreshold or isCliff then
					
					
				end


			end
		end
	end

	-- Periodically update the heightmap
	lastHeightmapUpdate = lastHeightmapUpdate + deltaTime
	if lastHeightmapUpdate >= simulationParameters.heightmapUpdateInterval then
		lastHeightmapUpdate = 0

		-- Update the heightmap
		heightmap = generateTerrainHeightmap(sim)
		sim:cellEncodeHeightmap(heightmap, 1)
	end
end)

Module:-- GMFluxContainer ModuleScript

local GMFluxContainer = {}
GMFluxContainer.__index = GMFluxContainer

-- FluxCell constants
GMFluxContainer.FluxCell = {
	Height = 0,
	Depth = 1,
	FluxLD = 2,
	FluxRU = 3,
}

-- Constructor
function GMFluxContainer.new(texSize, position, cellSize, parameters)
	local self = setmetatable({}, GMFluxContainer)

	-- Simulation parameters
	self.fluxStrength = parameters.fluxStrength or 0.250
	self.fluxAtten = parameters.fluxAtten or 0.975
	self.fluxSpill = parameters.fluxSpill or 0.0

	self.texSize = texSize or 64
	self.cellSize = cellSize or 2

	-- Position of the fluid grid in the world
	self.position = position or Vector3.new(0, 0, 0)

	-- Initialize data structures
	self.cells = {}
	for x = 1, self.texSize do
		self.cells[x] = {}
		for y = 1, self.texSize do
			self.cells[x][y] = {
				[GMFluxContainer.FluxCell.Height] = 0,
				[GMFluxContainer.FluxCell.Depth] = 0,
				[GMFluxContainer.FluxCell.FluxLD] = {left = 0, down = 0},
				[GMFluxContainer.FluxCell.FluxRU] = {right = 0, up = 0},
			}
		end
	end

	return self
end

-- Function to perform a step of the flux simulation
function GMFluxContainer:stepFlux()
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local cell = self.cells[x][y]
			local depth = cell[GMFluxContainer.FluxCell.Depth]
			local height = cell[GMFluxContainer.FluxCell.Height]
			local totalHeight = height + depth

			-- Neighbor positions
			local neighbors = {
				left = x > 1 and self.cells[x - 1][y] or nil,
				right = x < self.texSize and self.cells[x + 1][y] or nil,
				down = y > 1 and self.cells[x][y - 1] or nil,
				up = y < self.texSize and self.cells[x][y + 1] or nil,
			}

			-- Initialize fluxes
			local fluxes = {left = 0, right = 0, down = 0, up = 0}
			local totalFluxOut = 0

			-- Compute fluxes to neighbors
			for dir, neighbor in pairs(neighbors) do
				if neighbor then
					local neighborTotalHeight = neighbor[GMFluxContainer.FluxCell.Height] + neighbor[GMFluxContainer.FluxCell.Depth]
					local fluxComponent = (dir == 'left' or dir == 'down') and cell[GMFluxContainer.FluxCell.FluxLD][dir] or cell[GMFluxContainer.FluxCell.FluxRU][dir]
					local deltaHeight = totalHeight - neighborTotalHeight
					local flux = self.fluxAtten * fluxComponent + self.fluxStrength * math.max(0, deltaHeight)
					fluxes[dir] = flux
					totalFluxOut = totalFluxOut + flux
				end
			end

			-- Limit total flux to available depth
			local maxFlux = math.max(0, depth - self.fluxSpill)
			if totalFluxOut > maxFlux and totalFluxOut > 0 then
				local scale = maxFlux / totalFluxOut
				for dir in pairs(fluxes) do
					fluxes[dir] = fluxes[dir] * scale
				end
			end

			-- Update fluxes in cell
			local fluxLD = cell[GMFluxContainer.FluxCell.FluxLD]
			local fluxRU = cell[GMFluxContainer.FluxCell.FluxRU]
			fluxLD.left = fluxes.left
			fluxLD.down = fluxes.down
			fluxRU.right = fluxes.right
			fluxRU.up = fluxes.up
		end
	end
end

-- Function to perform a depth update step
function GMFluxContainer:stepDepth()
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local cell = self.cells[x][y]
			local depth = cell[GMFluxContainer.FluxCell.Depth]
			local fluxOut = cell[GMFluxContainer.FluxCell.FluxLD].left + cell[GMFluxContainer.FluxCell.FluxLD].down +
				cell[GMFluxContainer.FluxCell.FluxRU].right + cell[GMFluxContainer.FluxCell.FluxRU].up
			local fluxIn = 0

			-- Flux in from neighbors
			if x > 1 then
				fluxIn = fluxIn + self.cells[x - 1][y][GMFluxContainer.FluxCell.FluxRU].right
			end
			if x < self.texSize then
				fluxIn = fluxIn + self.cells[x + 1][y][GMFluxContainer.FluxCell.FluxLD].left
			end
			if y > 1 then
				fluxIn = fluxIn + self.cells[x][y - 1][GMFluxContainer.FluxCell.FluxRU].up
			end
			if y < self.texSize then
				fluxIn = fluxIn + self.cells[x][y + 1][GMFluxContainer.FluxCell.FluxLD].down
			end

			-- Update depth
			local newDepth = depth - fluxOut + fluxIn
			newDepth = math.max(newDepth, 0)
			cell[GMFluxContainer.FluxCell.Depth] = newDepth
		end
	end
end

-- Function to handle collisions (Advanced collisions)
function GMFluxContainer:handleCollisions(collidableObjects)
	local cellSize = self.cellSize
	local basePosition = self.position

	for _, obj in ipairs(collidableObjects) do
		local objPos = obj.Position
		local objSizeHalf = obj.Size * 0.5

		local minX = math.floor((objPos.X - objSizeHalf.X - basePosition.X) / cellSize) + 1
		local maxX = math.floor((objPos.X + objSizeHalf.X - basePosition.X) / cellSize) + 1
		local minY = math.floor((objPos.Z - objSizeHalf.Z - basePosition.Z) / cellSize) + 1
		local maxY = math.floor((objPos.Z + objSizeHalf.Z - basePosition.Z) / cellSize) + 1

		-- Clamp to grid boundaries
		minX = math.max(1, minX)
		maxX = math.min(self.texSize, maxX)
		minY = math.max(1, minY)
		maxY = math.min(self.texSize, maxY)

		for x = minX, maxX do
			for y = minY, maxY do
				local cell = self.cells[x][y]
				local height = cell[GMFluxContainer.FluxCell.Height]
				local depth = cell[GMFluxContainer.FluxCell.Depth]
				local cellWorldPosX = basePosition.X + (x - 0.5) * cellSize
				local cellWorldPosZ = basePosition.Z + (y - 0.5) * cellSize

				-- Check if object overlaps with the cell
				if math.abs(objPos.X - cellWorldPosX) <= (objSizeHalf.X + cellSize / 2) and
					math.abs(objPos.Z - cellWorldPosZ) <= (objSizeHalf.Z + cellSize / 2) then

					-- If the object's bottom is below the fluid surface, adjust fluid depth
					local objBottomY = objPos.Y - objSizeHalf.Y
					local objTopY = objPos.Y + objSizeHalf.Y
					if objBottomY <= height + depth then
						local maxDepth = math.max(0, objBottomY - height)
						cell[GMFluxContainer.FluxCell.Depth] = math.min(depth, maxDepth)
					end
				end
			end
		end
	end
end

-- Encode terrain heightmap
function GMFluxContainer:cellEncodeHeightmap(heightmap, factor)
	for x = 1, self.texSize do
		for y = 1, self.texSize do
			local height = heightmap[x][y] * factor
			self.cells[x][y][GMFluxContainer.FluxCell.Height] = height
		end
	end
end

-- Add fluid at a specific position
function GMFluxContainer:addFluid(x, y, amount)
	if x >= 1 and x <= self.texSize and y >= 1 and y <= self.texSize then
		self.cells[x][y][GMFluxContainer.FluxCell.Depth] = self.cells[x][y][GMFluxContainer.FluxCell.Depth] + amount
	end
end

-- Get fluid depth at a position
function GMFluxContainer:getFluidDepthAt(x, y)
	if x >= 1 and x <= self.texSize and y >= 1 and y <= self.texSize then
		return self.cells[x][y][GMFluxContainer.FluxCell.Depth]
	else
		return 0
	end
end

return GMFluxContainer