@dave1707 ’s voxel-terrain algorithm is really great and versatile. Thanks dave. And here’s yet another iteration on it.
No voxels here—this uses the algorithm to generate a craft terrain model as seen above.
-- Global references for scene and terrain
local scene, terrainModel, terrainEntity
function setup()
perlinH = craft.noise.perlin()
setupScene()
generateTerrain()
prepareTerrainModel()
end
function setupScene()
-- Create a new Craft scene
scene = craft.scene()
-- Add an OrbitViewer for camera control
local viewer = scene.camera:add(OrbitViewer, vec3(275, 200, 200), 3000, 0, 2000)
viewer.camera.farPlane = 10000
viewer.rx, viewer.ry = 50, -40
-- Adjust the sun (directional light) properties
scene.sun.rotation = quat.eulerAngles(45, 0, 116) -- Adjust the direction of the sun
scene.sun:get(craft.light).intensity = 0.8 -- Adjust sunlight intensity
-- Add an ambient light to the scene
scene.ambientColor = color(127, 127, 127, 255) -- Adjust the ambient light color and intensity
end
function calculateNormal(v1, v2, v3)
local edge1 = v2 - v1
local edge2 = v3 - v1
return edge1:cross(edge2):normalize()
end
function prepareTerrainModel()
terrainEntity = scene:entity()
terrainEntity.model = terrainModel
terrainEntity.material = craft.material(asset.builtin.Materials.Basic)
terrainEntity.material.map = readImage(asset.builtin.Surfaces.Desert_Cliff_Color)
end
function draw()
scene:update(DeltaTime)
scene:draw()
end
function generateTerrain()
-- Terrain generation settings
local pointsPerSide = 400
local offsetX, offsetZ = math.random(500), math.random(500)
-- Prepare for terrain generation
local terrainVerts, terrainIndices, terrainNormals, terrainUvs, terrainColors = {}, {}, {}, {}, {}
local m = 1 / pointsPerSide
local h = perlinH
-- First, generate raw height values
local rawHeights = {}
generateHeightValues(rawHeights, pointsPerSide, offsetX, offsetZ, perlinH)
-- Apply a simple smoothing pass to the height values
local smoothedHeights = smoothHeights(rawHeights, pointsPerSide)
-- Use smoothedHeights for generating terrain vertices, normals, etc.
generateVertsNormalsUvsColors(pointsPerSide, smoothedHeights,
terrainVerts, terrainNormals, terrainUvs, terrainColors)
-- Generate terrain indices for mesh triangles
generateTerrainIndices(pointsPerSide, terrainIndices)
local smoothedNormals = generateSmoothedNormals(terrainIndices, terrainVerts, terrainNormals)
-- Finalize terrain model
terrainModel = craft.model()
terrainModel.positions = terrainVerts
terrainModel.indices = terrainIndices
terrainModel.normals = smoothedNormals
terrainModel.uvs = terrainUvs
terrainModel.colors = terrainColors
end
function generateHeightValues(
valuesTable, pointsPerSide, offsetX, offsetZ, perlinH)
local rawHeights = valuesTable
local m = 1 / pointsPerSide
for x = 1, pointsPerSide do
rawHeights[x] = {}
for z = 1, pointsPerSide do
local xx, zz = x * m + offsetX, z * m + offsetZ
local y = math.abs(perlinH:getValue(xx, 0, zz)) * 150
rawHeights[x][z] = y > 127 and 127 - (y - 127) or y
end
end
end
function generateVertsNormalsUvsColors(pointsPerSide, smoothedHeights, terrainVerts, terrainNormals, terrainUvs, terrainColors)
-- Determine the maximum height value
local maxHeight = 0
for x = 1, pointsPerSide do
for z = 1, pointsPerSide do
local height = smoothedHeights[x][z]
if height > maxHeight then
maxHeight = height
end
end
end
-- Define the three key levels and their associated colors
local lowestColor = color(90, 53, 42)
local middleColor = color(219, 148, 101)
local highestColor = color(215, 174, 127)
-- Generate vertices, normals, uvs, and colors with gradient
for x = 1, pointsPerSide do
for z = 1, pointsPerSide do
local y = smoothedHeights[x][z]
local index = (x - 1) * pointsPerSide + z
terrainVerts[index] = vec3(x, y, z)
terrainNormals[index] = vec3(0, 1, 0)
terrainUvs[index] = vec2(x / pointsPerSide, z / pointsPerSide)
-- Calculate relative height (0.0 to 1.0)
local relativeHeight = y / maxHeight
-- Determine the color based on the relative height
if relativeHeight < 0.5 then
-- Interpolate between lowestColor and middleColor
local t = (relativeHeight * 2) -- Scale factor for lower half
terrainColors[index] = lerpColor(lowestColor, middleColor, t)
else
-- Interpolate between middleColor and highestColor
local t = (relativeHeight - 0.5) * 2 -- Scale factor for upper half
terrainColors[index] = lerpColor(middleColor, highestColor, t)
end
end
end
end
-- Function to interpolate between two colors
function lerpColor(c1, c2, t)
local r = math.floor(lerp(c1.r, c2.r, t))
local g = math.floor(lerp(c1.g, c2.g, t))
local b = math.floor(lerp(c1.b, c2.b, t))
local a = math.floor(lerp(c1.a, c2.a, t))
return color(r, g, b, a)
end
-- Linear interpolation function
function lerp(a, b, t)
return a + (b - a) * t
end
function generateSmoothedNormals(terrainIndices, terrainVerts, terrainNormals)
local smoothingFactor = 1 -- Range from 0 to 1, adjust this to control smoothing
-- Assume vertices and indices are already populated
local faceNormals = {}
local vertexFaces = {}
-- Calculate face normals
for i = 1, #terrainIndices, 3 do
local i1, i2, i3 = terrainIndices[i], terrainIndices[i+1], terrainIndices[i+2]
local v1, v2, v3 = terrainVerts[i1], terrainVerts[i2], terrainVerts[i3]
local normal = calculateNormal(v1, v2, v3)
faceNormals[#faceNormals + 1] = normal
-- Map vertices to their faces
vertexFaces[i1] = vertexFaces[i1] or {}
vertexFaces[i2] = vertexFaces[i2] or {}
vertexFaces[i3] = vertexFaces[i3] or {}
table.insert(vertexFaces[i1], #faceNormals)
table.insert(vertexFaces[i2], #faceNormals)
table.insert(vertexFaces[i3], #faceNormals)
end
-- Calculate smoothed vertex normals
local smoothedNormals = {}
for i, v in ipairs(terrainVerts) do
local sumNormal = vec3(0, 0, 0)
local faces = vertexFaces[i] or {}
for _, fIndex in ipairs(faces) do
sumNormal = sumNormal + faceNormals[fIndex]
end
if #faces > 0 then
local avgNormal = (sumNormal / #faces):normalize()
-- Interpolate between the original normal and the smoothed normal
smoothedNormals[i] = lerp(terrainNormals[i], avgNormal, smoothingFactor)
else
smoothedNormals[i] = terrainNormals[i]
end
end
return smoothedNormals
end
function generateTerrainIndices(pointsPerSide, terrainIndices)
for i = 1, pointsPerSide - 1 do
for j = 1, pointsPerSide - 1 do
local base = (i - 1) * pointsPerSide + j
local nextRow = base + pointsPerSide
-- Triangle 1
table.insert(terrainIndices, base)
table.insert(terrainIndices, nextRow)
table.insert(terrainIndices, base + 1)
-- Triangle 2
table.insert(terrainIndices, nextRow)
table.insert(terrainIndices, nextRow + 1)
table.insert(terrainIndices, base + 1)
end
end
end
function smoothHeights(heights, size)
local smoothed = {}
for x = 1, size do
smoothed[x] = {}
for z = 1, size do
local sum = 0
local count = 0
for i = -1, 1 do
for j = -1, 1 do
if heights[x + i] and heights[x + i][z + j] then
sum = sum + heights[x + i][z + j]
count = count + 1
end
end
end
smoothed[x][z] = sum / count
end
end
return smoothed
end