Hi!
I am trying to create an infinite road generation system, but so far no luck.
Basically what I need, is a system that creates an infinite road system, with highways, intersections, interchanges, dirt roads etc.
I use terrain as the ground, and I am using nodes to create each road. Each node can be at a different height it all depends on whether the terrain is flat or hilly or whatever.
Currently, each “chunk” represents a part of the road, and in each chunk there are 16x16=256 nodes, that are used to ensure that the road curves with the terrain, resulting in an bumpy or hilly road or whatever. (This is what I want btw)
However, so far, the nodes aren’t placed directly on top of the terrain, which makes the road act the same way, it stays flat, and doesn’t realistically touch the ground in some areas.
In the image you can also see that the road, doesn’t really have nice curves, rather it starts exactly on the left top node. Please also note that I made it so that the nodes on the side, have to be shared across the bordering chunk(s).
As you can see in the image below, only the top left node of the first chunk and the bottom right node of the next chunk are shared, instead of all the top nodes and all the bottom nodes.
What I basically want to achieve with the nodes, is assume that you are in blender, and you have a cube, with 16x16 vertices on the top, now if you grab some of them, the mesh deforms with it. This is basically what I want to mimic or directly replicate if possible (don’t think it is) in Roblox Studio.
EDIT: Forgot to put the script:
wait(7)
-- ROAD TYPES CONFIGURATION
local roadTypes = {
["asphalt"] = {
material = Enum.Material.Asphalt,
color = Color3.fromRGB(50, 50, 50),
thickness = 0.2, -- Thickness in studs
width = 33, -- Road width in studs
length = 64, -- Road length in studs
maxHeightDifference = 10, -- Max height difference between nodes in a chunk
isTerrainPainted = false, -- Asphalt uses parts, not terrain
},
-- Add more road types here as needed (e.g., dirt road, gravel, etc.)
}
-- SETTINGS
local seed = 12345 -- Change to your desired seed for reproducibility
math.randomseed(seed)
local settings = {
maxHighways = 5, -- Maximum number of highways
maxHighwayLength = 5000, -- Max highway length (in studs) before creating an interchange
maxRoadAngle = 65, -- Max angle for road turns
maxIntersectionAngle = 90, -- Max angle for intersections
defaultRoadType = "asphalt", -- Default road type
highwayRoadType = "asphalt", -- Default highway type
}
-- HELPER FUNCTIONS
local function getTerrainHeightAt(position)
-- Get the terrain height at a given position
local terrain = workspace.Terrain
local region = Region3.new(position - Vector3.new(2, 2, 2), position + Vector3.new(2, 2, 2))
local voxelResolution = 4 -- Voxel resolution level
local voxelData, positions = terrain:ReadVoxels(region, voxelResolution)
-- Ensure voxelData and positions are valid
if voxelData and voxelData[1] and voxelData[1][1] and voxelData[1][1][1] then
local material, pos = voxelData[1][1][1], positions[1][1][1]
if pos then
return pos.Y -- Return the Y-coordinate of the position
end
end
-- Default to a height of 0 if no voxel data is available
return 0
end
local function createNodeVisual(position)
-- Create a visual node
local sphere = Instance.new("Part")
sphere.Shape = Enum.PartType.Ball
sphere.Size = Vector3.new(0.5, 0.5, 0.5)
sphere.Anchored = true
sphere.Material = Enum.Material.Neon
sphere.Color = Color3.fromRGB(255, 0, 0) -- Red Neon Color
sphere.Position = position
sphere.Parent = workspace
end
-- Generates a single road chunk
local function generateRoadChunk(origin, roadType)
local config = roadTypes[roadType]
local nodes = {}
local width = config.width
local length = config.length
local thickness = config.thickness
local nodeSpacingX = width / 15 -- Spacing between nodes in the X direction
local nodeSpacingZ = length / 15 -- Spacing between nodes in the Z direction
-- Generate 16x16 nodes across the surface
for x = 0, 15 do
nodes[x] = {}
for z = 0, 15 do
local position = origin + Vector3.new(x * nodeSpacingX, 0, z * nodeSpacingZ)
local terrainHeight = getTerrainHeightAt(position)
position = Vector3.new(position.X, terrainHeight, position.Z)
-- Store node position
nodes[x][z] = position
-- Create a visual for the node
createNodeVisual(position)
end
end
-- Create road parts between nodes
for x = 0, 14 do
for z = 0, 14 do
local node1 = nodes[x][z]
local node2 = nodes[x + 1][z]
local node3 = nodes[x][z + 1]
local node4 = nodes[x + 1][z + 1]
-- Create a part to fill the space between the nodes
local roadPart = Instance.new("Part")
roadPart.Size = Vector3.new(nodeSpacingX, thickness, nodeSpacingZ)
roadPart.Anchored = true
roadPart.Material = config.material
roadPart.Color = config.color
-- Calculate the average height for the part's center
local avgHeight = (node1.Y + node2.Y + node3.Y + node4.Y) / 4
roadPart.CFrame = CFrame.new(
(node1 + node2 + node3 + node4) / 4,
Vector3.new(0, avgHeight, 0)
)
roadPart.Parent = workspace
end
end
-- Return edge nodes for connecting chunks
local edgeNodes = {}
for z = 0, 15 do
table.insert(edgeNodes, nodes[15][z]) -- Right edge
end
for x = 0, 15 do
table.insert(edgeNodes, nodes[x][15]) -- Bottom edge
end
return edgeNodes
end
-- Main road generation function
local function generateRoadSystem(startPosition)
local currentPosition = startPosition
local currentDirection = Vector3.new(1, 0, 0) -- Default direction
local previousRoadType = settings.defaultRoadType
local highwayCount = 0
local previousEdgeNodes = {}
for i = 1, settings.maxHighwayLength do
-- Decide next road type
local roadType = previousRoadType
-- Generate road chunk
local edgeNodes = generateRoadChunk(currentPosition, roadType)
-- Shift position for next chunk
currentPosition = edgeNodes[#edgeNodes]
-- Update edge nodes for the next chunk
previousEdgeNodes = edgeNodes
-- Handle interchanges and intersections
if math.random(1, 100) < 10 then -- 10% chance of an intersection
highwayCount = highwayCount + 1
if highwayCount >= settings.maxHighways then
break
end
-- Create an intersection (can expand to generate additional roads)
local intersectionPosition = currentPosition + Vector3.new(math.random(-50, 50), 0, math.random(-50, 50))
currentPosition = intersectionPosition
end
-- Update the previous road type
previousRoadType = roadType
end
end
-- START GENERATION
generateRoadSystem(Vector3.new(0, 0, 0)) -- Start at the origin
Any tips/help is appreciated!