Having problems generating trees for my procedural planets!

Its been a while i’m trying to make a procedural planet (as sphere) generator. After a few days, i managed to came up with a working sphere planet generator (thanks to other posts on the devforum). However i really don’t know how i could implement a tree positioning system. I already tried but got no luck. Here is the code, hope it can help!

local steps = 0
local layer = 0

local origin = workspace.PlanetCenter.Position
local diameter = 1500
local segmentSize = Vector3.new(30, 30, 10)

local seed = math.random(0,10e6)

local noiseScale = 1/math.random(150, 250)
local amplitude = math.random(40, 130)
local meshDirectory = game.ReplicatedStorage.PlanetAssets

function getMaterial(height)
	if height <= 50 then
		if math.random(1,math.random(6,15)) == 3 then
			return Enum.Material.Rock
		else
			return Enum.Material.Grass
		end
	elseif height >= 100 then
		return Enum.Material.Rock
	end

	if math.random(1,math.random(4,10)) == 1 then
		return Enum.Material.Rock
	else
		return Enum.Material.Snow
	end
end

local radius = diameter / 2


repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local cx, cy, cz = position.Position.x, position.Position.y, position.Position.z

		local noise = math.noise(cx * noiseScale, cy * noiseScale, cz * noiseScale, seed) * amplitude
		local newRadius = radius + noise
		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, getMaterial(noise))
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer + 1
	task.wait()
until layer == 90
print('done 1')

steps = 0
layer = 0
repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local cx, cy, cz = position.Position.x, position.Position.y, position.Position.z

		local noise = math.noise(cx * noiseScale, cy * noiseScale, cz * noiseScale, seed) * amplitude
		local newRadius = radius + noise
		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, getMaterial(noise))

		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer - 1
	task.wait()
until layer == -90
print('done 2')

Thanks, gianfragolo

After looking quickly for a solution on google, i found something for you. Maybe it will helps you:

Still dosen’t work sadly it generates only a few trees and the rest spawn in mid air or not correctly. Probably because we are talking about a spheric planet.

I cannot explain exactly how but what you will need for this is to get the CFrame of your planet and work around with it + the size/2 of the axis it should spawn on. But if this is a perfect sphere, only one axis /2 (Size) should be needed

you can use 2d poisson disk but you would need to wrap it around the planet using math

I already thinked at that option but the problem is that it isn’t just a flat sphere: it’s a planet with hills and mountain made out of smooth terrain.

The best solution would then to take a random CFrame based of your planet’s CF, a random and abusive Y position and raycast from the tree to the planet

Wait really? How that would be done exactly? I don’t have much experience in raycasting. Do you know any other post / code snippets i could look on please?

to do a Raycast, you just need 3 things:

  1. The origin position
  2. the direction from the origin
  3. raycast parameters

It looks like something like this:

local origin = OriginPosition
local cf = CFrame.new(origin, Destination) --most devs will suggest you to use CFrame.LookAt()
local direction = cf.LookVector * 50 --i used 50 in my script but the distance could be different, up to you
				
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {Character, Folder} --Anything that shouldn't be calculated on the raycast
			
local result = workspace:Raycast(origin, direction, params)
				
if result then
 --result here
					
end

Also, on RaycastFilterType i used Exclude but you could use something like Include which will calculate only things on FilterDescendantsInstances

so offset the distance from the center by the noise value at that specific angle

Yeah okay, but thos are the really basic of raycasting (taht i already knew) i was talking about how i could implement in my current system the raycast method that you suggested (sorry if the previous message was unclear)

I would suggest (not sure if my methode is the best) to take the CFrame of the planet, random each axis, set the distance at one axis.Size and raycast from there to the planet

And right now i came up with the idea to simply use .LookVector to set the tree CFrame. Now the position works well however the orientation of the model is completely broken. Here is the code :slight_smile:

	local tree = game.ReplicatedStorage.PlanetAssets.Trees.T1.BeechwoodTree_Var01:Clone()
	tree:PivotTo(newCF, newCF.LookVector * 2)
	tree.Parent = workspace

And here’s how it looks:

I did this recently.

I did it by setting them the distance away from the center of the planet of the radius. Then I used LookVector to make them point away from the center of the planet.

wow that is cool all tho i have to ask why are you making procedural planets

You’ll need to make sure the pivot point of the tree is at the base. That way you don’t need to account for shapes and sizes of different trees.

No mans blox… :wink:

Here are a few more cool pics:


so cool, I tried to do something like this a while back


but the trees just did not want to work

Looks really good! Its pretty simple on how to do the trees, I can give you the code if you want:

local function spawnTrees(center, radius, numSurfaceParts)
	local throttle = 0
	local randColor = BrickColor.random()
	for i = 1, numSurfaceParts do
		local phi = math.random() * math.pi
		local theta = math.random() * 2 * math.pi

		local x = radius * math.sin(phi) * math.cos(theta)
		local y = radius * math.sin(phi) * math.sin(theta)
		local z = radius * math.cos(phi)

		-- Adjust the Y component to spawn above the planet based on the distance
		local position = center + Vector3.new(x*1.025, y*1.025, z*1.025)
		local trees = game.ReplicatedStorage.Trees:GetChildren()	
		local model = trees[math.random(1, #trees)]:Clone()
		local surfacePart = model.PrimaryPart
		surfacePart.CFrame = CFrame.new(position)
		surfacePart.Anchored = true
		model.Parent = workspace

		-- Make the part face away from the center
		surfacePart.CFrame = CFrame.lookAt(position, center)

		-- You can add additional customization for the surface parts here
		throttle = throttle + 1

		if throttle == 100 then
			throttle = 0
			task.wait()
		end
	end
end