I'm looking to render grass in my game, but i decided it would be best to just do it on the client so I could choose to only show grass in a set radius.

I'm not sure where to start, how would I consistently place the grass(e.g. grass leaves radius, comes back in and remains in the same spot) should I generate and store all the points or would using a seed work? If I stored all the points in a table I'm not sure if I want to iterate over the entire thing every time the player moves to check if grass is in an area. I'm sure there's a way to organize my points, like in chunks but that's something I've never done..

tl;dr How would you locally generate grass in a set radius around a player?

**Edit: ** I've come up with a solution, but I'm still open to more efficient ideas.

Currently I'm generating all the grass positions in clusters, I take a table place a random point in it, and then from that point I generate more random points within an area and place those in another table within that table so it looks something like: {{point = vector3, grass= {vector3, vector3, vector3}}

From that I can then check for clusters near the camera and render the grass associated with them. It seems to handle 10,000 grass pieces alright, but sometimes the fps dip down for a moment when instancing the grass. I've tested up to 20,000 and have faired alright, but the frame rate drops lower more noticeably when instancing parts. Maybe I can find a way to recycle parts, or sort my data better.

If anyone is interested in the code:

local renderDistance = 100 -- studs radius

local player = game.Players.LocalPlayer
local camera = Workspace.CurrentCamera 
local base = Workspace.Test

math.randomseed(tick())

function randomPoint(center, size)
	return Vector3.new(
		math.random(center.X - size.X/2, center.X + size.X/2),
		center.Y + size.Y/2,
		math.random(center.Z - size.Z/2, center.Z + size.Z/2)
	)	
end

function inTable(tab, val)
	for i,v in pairs(tab) do
		if v == val then
			return i
		end
	end
	return nil
end
function fade(part, dir)
	if dir == "In" then
		Spawn(function()
			for i = 1, 0.5, -0.05 do --bleh, too tired to make this function useful...
				part.Transparency = i
				wait()
			end
		end)
	elseif dir == "Out" then
		Spawn(function()
			for i = 0, 1, 0.05 do
				part.Transparency = i
				wait()
			end
		end)
	end
end

function grass(cf, size)
	local g = Instance.new("Part")
	g.Anchored = true
	g.CanCollide = false
	g.Name = "Grass"
	g.BrickColor = BrickColor.new("Grime")
	g.Transparency = 1
	g.Size = size
	g.CFrame = cf
	g.Parent = camera
	local mesh = Instance.new("SpecialMesh", g)
	mesh.MeshType = "FileMesh"
	mesh.MeshId = "http://www.roblox.com/asset/?id=17659272"
	mesh.Scale = (size/2)
	return g
end

local grasses = {} --{{Vector3.new(clusterPosition),{grass, grass, grass}},{Vector3.new(clusterPosition),{grass, grass, grass}}}
local num = 10000
local clusters = 0
while num > 0 do
	--grass cluster
	clusters = clusters + 1
	local clusterPoint = randomPoint(base.Position, base.Size)
	local clusterSize = math.random(5,100)
	grasses[clusters] = {point=clusterPoint, points = {},grass = {}}
	for i = 5, clusterSize do
		local cf = CFrame.new(randomPoint(clusterPoint, Vector3.new(clusterSize, base.Size.Y,clusterSize)))
		* CFrame.Angles(math.rad(-45),math.rad(1,360),0) 
		- Vector3.new(0,0.25,0)
		grasses[clusters].points[i] = cf  
		grass(cf, Vector3.new(1,1,1)*math.random(3,4))
	end
	num = num - clusterSize
end

--render grass
local camPos = camera.CoordinateFrame
local visibleClusters = {}
while wait(0.2) do
	if camPos ~= camera.CoordinateFrame then
		camPos = camera.CoordinateFrame
		for i,v in pairs(grasses) do
			local dist = (camPos.p - v.point).magnitude
			local visible = inTable(visibleClusters, v.point)
			if dist > renderDistance and visible then
				--hide cluster
				for _,g in pairs(v.grass) do
					g:Destroy()
				end
				table.remove(visibleClusters, visible)
			elseif dist < renderDistance and not visible then
				table.insert(visibleClusters, v.point)
				for k,pos in pairs(v.points) do
					v.grass[k] = grass(pos, Vector3.new(3,3,3))
					fade(v.grass[k], "In")
				end
			end
		end
	end
end