You can write your topic however you want, but you need to answer these questions:
- What do you want to achieve? Keep it simple and clear!
I want to fix a bug in my terrain generation system where some tree models float above the ground or have parts below the ground. This issue has been persistent since I started developing the system, which is heavily inspired by the one made by Okeanskiy.
- What is the issue? Include screenshots / videos if possible!
The problem is that some of the trees are floating above the chunks or the ground, while others have parts buried underground. This affects all tree models. Below is an image of the issue:
- What solutions have you tried so far? Did you look for solutions on the Developer Hub?
I have searched through the Developer Hub and the Developer Forum but have not found any posts that address a similar issue. I have tried several adjustments in my script, but the problem persists.
local X, Z = 4, 4
local TERRAIN_HEIGHT_COLORS = {
[-50] = Color3.fromRGB(31, 85, 18);
[-10] = Color3.fromRGB(72, 113, 58);
[0] = Color3.fromRGB(72, 113, 58);
}
local BLACK_ENERGY_COLORS = {
[1] = Color3.fromRGB(12, 17, 101);
[2] = Color3.fromRGB(45, 77, 128);
[3] = Color3.fromRGB(24, 82, 130);
}
local WIDTH_SCALE = math.random(15, 30)
local HEIGHT_SCALE = 100
local TERRAIN_SMOOTHNESS = 20
local MIN_TREE_SPAWN_HEIGHT = -60
local MAX_TREE_SPAWN_HEIGHT = 30
local TREE_DENSITY = math.random(2.5,4)
local SEED = math.random(10000000)
local wedge = Instance.new("WedgePart");
wedge.Anchored = true;
wedge.TopSurface = Enum.SurfaceType.Smooth;
wedge.BottomSurface = Enum.SurfaceType.Smooth;
local function draw3dTriangle(a, b, c)
local ab, ac, bc = b - a, c - a, c - b;
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc);
if (abd > acd and abd > bcd) then
c, a = a, c;
elseif (acd > bcd and acd > abd) then
a, b = b, a;
end
ab, ac, bc = b - a, c - a, c - b;
local right = ac:Cross(ab).unit;
local up = bc:Cross(right).unit;
local back = bc.unit;
local height = math.abs(ab:Dot(up));
local w1 = wedge:Clone();
w1.Size = Vector3.new(0, height, math.abs(ab:Dot(back)));
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back);
w1.Parent = workspace;
local w2 = wedge:Clone();
w2.Size = Vector3.new(0, height, math.abs(ac:Dot(back)));
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back);
w2.Parent = workspace;
return w1, w2;
end
local function getHeight(chunkPosX, chunkPosZ, x, z)
return math.noise(
(X/TERRAIN_SMOOTHNESS * chunkPosX) + x/TERRAIN_SMOOTHNESS,
(Z/TERRAIN_SMOOTHNESS * chunkPosZ) + z/TERRAIN_SMOOTHNESS,
SEED
) * HEIGHT_SCALE
end
local function getPosition(chunkPosX, chunkPosZ, x, z)
return Vector3.new(
chunkPosX*X*WIDTH_SCALE + x*WIDTH_SCALE,
getHeight(chunkPosX, chunkPosZ, x, z),
chunkPosZ*Z*WIDTH_SCALE + z*WIDTH_SCALE
)
end
local function paintWedge(wedge, event)
local wedgeHeight = wedge.Position.Y
if event == "NormalChunk" then
local color
local lowerColorHeight
local higherColorHeight
for height, heightColor in pairs(TERRAIN_HEIGHT_COLORS) do
if wedgeHeight == height then
color = heightColor
break
end
if (wedgeHeight < height) and (not higherColorHeight or height < higherColorHeight) then
higherColorHeight = height
end
if (wedgeHeight > height) and (not lowerColorHeight or height > lowerColorHeight) then
lowerColorHeight = height
end
end
if not color then
if higherColorHeight == nil then
color = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
elseif lowerColorHeight == nil then
color = TERRAIN_HEIGHT_COLORS[higherColorHeight]
else
local alpha = (wedgeHeight - lowerColorHeight) / (higherColorHeight - lowerColorHeight)
local lowerColor = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
local higherColor = TERRAIN_HEIGHT_COLORS[higherColorHeight]
color = lowerColor:lerp(higherColor, alpha)
end
end
wedge.Material = Enum.Material.Grass
wedge.Color = color
elseif event == "DarkChunk" then
local colorIndex = math.random(1, #BLACK_ENERGY_COLORS)
local selectedColor = BLACK_ENERGY_COLORS[colorIndex]
wedge.Material = Enum.Material.Neon
wedge.Color = selectedColor
end
end
local function getTerrainNormal(posGrid, x, z)
-- Asegúrate de que los índices no salgan fuera de los límites del grid
if x <= 0 or x >= X - 1 or z <= 0 or z >= Z - 1 then
return Vector3.new(0, 1, 0) -- Normal estándar si está en el borde
end
local pos = posGrid[x][z]
local right = posGrid[x + 1][z] - pos
local forward = posGrid[x][z + 1] - pos
local normal = right:Cross(forward).unit
return normal
end
function isTooClose(treePositions, x, z, minDist)
for _, pos in pairs(treePositions) do
if (pos.X - x)^2 + (pos.Z - z)^2 < minDist^2 then
return true
end
end
return false
end
local function getHeightAtPosition(chunkPosX, chunkPosZ, x, z)
-- Calcular la posición exacta considerando los offsets dentro del chunk
local exactX = chunkPosX * X * WIDTH_SCALE + x
local exactZ = chunkPosZ * Z * WIDTH_SCALE + z
return math.noise(
(exactX / TERRAIN_SMOOTHNESS),
(exactZ / TERRAIN_SMOOTHNESS),
SEED
) * HEIGHT_SCALE
end
local function addTrees(chunk, chunkModel, event)
local posGrid = chunk.positionGrid
local instances = chunk.instances
local chunkPosX = chunk.x
local chunkPosZ = chunk.z
local treePositions = {}
local tree_Folder = nil
for x = 0, X-1 do
for z = 0, Z-1 do
local pos = posGrid[x][z]
if pos.Y >= MIN_TREE_SPAWN_HEIGHT and pos.Y <= MAX_TREE_SPAWN_HEIGHT then
math.randomseed(x * (chunkPosX + SEED) + z * (chunkPosZ + SEED))
if math.random() < TREE_DENSITY then
if event == "NormalChunk" then
tree_Folder = game.ReplicatedStorage.Normal_PineTrees:GetChildren()
elseif event == "DarkChunk" then
tree_Folder = game.ReplicatedStorage.Dark_Chunk:GetChildren()
end
if #tree_Folder > 0 then
local tree = tree_Folder[math.random(1, #tree_Folder)]:Clone()
print("Randomly selected tree: ", tree.Name)
for index, child in pairs(tree:GetChildren()) do
if child.Name == "Leaf" then
child.Color = Color3.fromRGB(
75 + math.random(-25, 25),
151 + math.random(-25, 25),
75 + math.random(-25, 25)
)
elseif child.Name == "Black_Leaf" then
child.Color = Color3.fromRGB(
0 + math.random(-10, 10),
0 + math.random(-10, 10),
200 + math.random(-50, 50)
)
end
end
local xOffset, zOffset, attempts = 0, 0, 0
repeat
xOffset = math.random(-WIDTH_SCALE * X / 2, WIDTH_SCALE * X / 2)
zOffset = math.random(-WIDTH_SCALE * Z / 2, WIDTH_SCALE * Z / 2)
attempts = attempts + 1
until (attempts > 50 or not isTooClose(treePositions, pos.X + xOffset, pos.Z + zOffset, 10)) -- Verificar la distancia mínima
if attempts <= 50 then
local OffSet = tree:GetAttribute("OffSet")
local baseOffset = tree.PrimaryPart.Size.Y/2
local normal = getTerrainNormal(posGrid, x, z)
local treePosition = Vector3.new(
pos.X + xOffset,
pos.Y + baseOffset,
pos.Z + zOffset
)
local upVector = Vector3.new(0, -1, 0)
local angleToUp = math.acos(normal:Dot(upVector))
local rotationAxis = upVector:Cross(normal).unit
local treeCFrame = CFrame.new(treePosition) * CFrame.fromAxisAngle(rotationAxis, angleToUp)
tree:SetPrimaryPartCFrame(treeCFrame)
tree.Parent = chunkModel
table.insert(instances, tree)
table.insert(treePositions, treePosition) -- Guardar la posición del árbol
else
warn("No se pudo colocar un árbol sin superposición después de varios intentos.")
end
else
warn("No trees found in the folder.")
end
end
end
end
end
end
local Chunk = {}
Chunk.__index = Chunk
Chunk.WIDTH_SIZE_X = X * WIDTH_SCALE
Chunk.WIDTH_SIZE_Z = Z * WIDTH_SCALE
function Chunk.new(chunkPosX, chunkPosZ, event)
local chunkModel = Instance.new("Model")
chunkModel.Name = "Chunk_" .. chunkPosX .. "_" .. chunkPosZ
chunkModel.Parent = workspace -- Asegúrate de asignar el parent adecuado para que sea visible y replicado
local chunk = {
instances = {};
positionGrid = {};
x = chunkPosX;
z = chunkPosZ;
}
setmetatable(chunk, Chunk)
local positionGrid = chunk.positionGrid
for x = 0, X do
positionGrid[x] = {}
for z = 0, Z do
positionGrid[x][z] = getPosition(chunkPosX, chunkPosZ, x, z)
end
end
for x = 0, X-1 do
for z = 0, Z-1 do
local a = positionGrid[x][z]
local b = positionGrid[x+1][z]
local c = positionGrid[x][z+1]
local d = positionGrid[x+1][z+1]
local wedgeA, wedgeB = draw3dTriangle(a, b, c)
local wedgeC, wedgeD = draw3dTriangle(b, c, d)
wedgeA.Parent = chunkModel
wedgeB.Parent = chunkModel
wedgeC.Parent = chunkModel
wedgeD.Parent = chunkModel
paintWedge(wedgeA, event)
paintWedge(wedgeB, event)
paintWedge(wedgeC, event)
paintWedge(wedgeD, event)
table.insert(chunk.instances, wedgeA)
table.insert(chunk.instances, wedgeB)
table.insert(chunk.instances, wedgeC)
table.insert(chunk.instances, wedgeD)
end
end
addTrees(chunk, chunkModel, event)
return chunk
end
function Chunk:Destroy()
for index, instance in ipairs(self.instances) do
instance:Destroy()
end
end
return Chunk