Last active
October 20, 2023 07:54
-
-
Save Softwave/d390272755a58a0714891517351e12ba to your computer and use it in GitHub Desktop.
TerrainCreator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# IslandCreator: A tool for generating island terrain meshes | |
# procedurally with Simplex Noise | |
@tool | |
extends Node3D | |
# Globals | |
var _noise = FastNoiseLite.new() | |
@export var groundMaterial: Material | |
# Properties | |
@export var islandRadius:float = 100.0: | |
set(value): | |
islandRadius = value | |
generateTerrain() | |
@export var islandFallOffRate:float = 0.001: | |
set(value): | |
islandFallOffRate = value | |
generateTerrain() | |
@export var minimumFalloff:float = 3.0: | |
set(value): | |
minimumFalloff = value | |
generateTerrain() | |
@export var waterDepth:float = 2.0: | |
set(value): | |
waterDepth = value | |
generateTerrain() | |
@export var overlapDistance:float = 300.0: | |
set(value): | |
overlapDistance = value | |
generateTerrain() | |
# Just use as a makeshift "rebuild" button | |
@export var build: bool = false: | |
set(value): | |
build = value | |
generateTerrain() | |
@export var period:int = 90: | |
set(value): | |
period = value | |
generateTerrain() | |
@export var octaves:int = 6: | |
set(value): | |
octaves = value | |
generateTerrain() | |
@export var yScale:int = 20: | |
set(value): | |
yScale = value | |
generateTerrain() | |
# subWidth and subHeight (subdivisions) are used to determine the number of vertices in the mesh | |
@export var subWidth:int = 100: | |
set(value): | |
subWidth = value | |
generateTerrain() | |
@export var subHeight:int = 100: | |
set(value): | |
subHeight = value | |
generateTerrain() | |
# The terrain width and height are used to determine the size of the mesh | |
@export var terrainWidth:int = 400: | |
set(value): | |
terrainWidth = value | |
generateTerrain() | |
@export var terrainHeight:int = 400: | |
set(value): | |
terrainHeight = value | |
generateTerrain() | |
func generateTerrain() -> void: | |
# Remove the previous MeshInstance3D, if it exists | |
if get_child_count() > 0: | |
get_child(0).queue_free() | |
# Setup Simplex Noise | |
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH | |
_noise.seed = randi() | |
_noise.fractal_octaves = octaves | |
_noise.frequency = 1.0 / period # Do we need this? | |
# Create new plane mesh | |
var planeMesh = PlaneMesh.new() | |
planeMesh.size = Vector2(terrainWidth, terrainHeight) | |
planeMesh.subdivide_depth = subWidth | |
planeMesh.subdivide_width = subHeight | |
# Setup SurfaceTool | |
var surfaceTool = SurfaceTool.new() | |
surfaceTool.create_from(planeMesh, 0) | |
var arrayPlane = surfaceTool.commit() | |
var dataTool = MeshDataTool.new() | |
dataTool.create_from_surface(arrayPlane, 0) | |
for i in range(dataTool.get_vertex_count()): | |
var vertex = dataTool.get_vertex(i) | |
# Get the distance from the center of the plane | |
var distanceFromCenter = vertex.distance_to(Vector3(0, 0, 0)) | |
var fallOff = getFalloff(distanceFromCenter) | |
var noiseInfluence = getNoiseInfluence(distanceFromCenter) | |
# Apply falloff based on distance from center | |
vertex.y = (_noise.get_noise_3d(vertex.x, vertex.y, vertex.z) * yScale * noiseInfluence + fallOff * yScale) | |
dataTool.set_vertex(i, vertex) | |
for i in range(arrayPlane.get_surface_count()): | |
arrayPlane.clear_surfaces() | |
dataTool.commit_to_surface(arrayPlane) | |
# Generate with SurfaceTool | |
surfaceTool.begin(Mesh.PRIMITIVE_TRIANGLES) | |
surfaceTool.create_from(arrayPlane, 0) | |
surfaceTool.generate_normals() | |
# Create new MeshInstance3D | |
var meshInstance = MeshInstance3D.new() | |
meshInstance.mesh = surfaceTool.commit() | |
# If we're in the editor, add the mesh to the scene tree | |
if Engine.is_editor_hint(): | |
self.add_child(meshInstance) | |
meshInstance.owner = self.get_tree().edited_scene_root | |
# Set the material | |
meshInstance.mesh.surface_set_material(0, groundMaterial) | |
func _ready() -> void: | |
# If we're in the editor, generate the terrain | |
if Engine.is_editor_hint(): | |
generateTerrain() | |
func getFalloff(distanceFromCenter: float) -> float: | |
# If we're inside the island, return a falloff that keeps the terrain flat | |
if distanceFromCenter <= islandRadius: | |
return 0.0 | |
# Start falloff at the edge of the islandRadius but gradually | |
if distanceFromCenter <= islandRadius + overlapDistance: | |
var t = (distanceFromCenter - islandRadius) / overlapDistance | |
return lerp(0.0, -minimumFalloff, t) | |
# For outside the overlap region, we do the same as before: | |
var maxDistance:float = Vector2(terrainWidth * 0.5, terrainHeight * 0.5).length() | |
var gradientDistance = maxDistance - islandRadius - overlapDistance | |
var t = (distanceFromCenter - islandRadius - overlapDistance) / gradientDistance | |
return lerpf(-minimumFalloff, -waterDepth, smoothstep(0.0, 1.0, t)) | |
func smoothstep(edge0: float, edge1: float, x: float) -> float: | |
# Scale, and clamp x to 0..1 range | |
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) | |
# Evaluate polynomial | |
return x * x * (3 - 2 * x) | |
func getNoiseInfluence(distanceFromCenter:float) -> float: | |
if distanceFromCenter <= islandRadius: | |
return 1.0 # Full influence inside the island radius | |
# Begin reducing the noise influence only after islandRadius + overlapDistance | |
if distanceFromCenter <= islandRadius + overlapDistance: | |
var t = (distanceFromCenter - islandRadius) / overlapDistance | |
return 1.0 - t # Gradual reduction based on overlap distance | |
return 0.0 # No influence beyond the overlap distance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TerrainCreator: A tool for generating terrain meshes procedurally with Simplex Noise | |
@tool | |
extends Node3D | |
# Globals | |
var _noise = FastNoiseLite.new() | |
@export var groundMaterial: Material | |
# Properties | |
@export var period:int = 50: | |
set(value): | |
period = value | |
generateTerrain() | |
@export var octaves:int = 6: | |
set(value): | |
octaves = value | |
generateTerrain() | |
@export var yScale:int = 20: | |
set(value): | |
yScale = value | |
generateTerrain() | |
# subWidth and subHeight (subdivisions) are used to determine the number of vertices in the mesh | |
@export var subWidth:int = 100: | |
set(value): | |
subWidth = value | |
generateTerrain() | |
@export var subHeight:int = 100: | |
set(value): | |
subHeight = value | |
generateTerrain() | |
# The terrain width and height are used to determine the size of the mesh | |
@export var terrainWidth:int = 200: | |
set(value): | |
terrainWidth = value | |
generateTerrain() | |
@export var terrainHeight:int = 200: | |
set(value): | |
terrainHeight = value | |
generateTerrain() | |
func generateTerrain() -> void: | |
# Remove the previous MeshInstance3D, if it exists | |
if get_child_count() > 0: | |
get_child(0).queue_free() | |
# Setup Simplex Noise | |
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH | |
_noise.seed = randi() | |
_noise.fractal_octaves = octaves | |
_noise.frequency = 1.0 / period # Do we need this? | |
# Create new plane mesh | |
var planeMesh = PlaneMesh.new() | |
planeMesh.size = Vector2(terrainWidth, terrainHeight) | |
planeMesh.subdivide_depth = subWidth | |
planeMesh.subdivide_width = subHeight | |
# Setup SurfaceTool | |
var surfaceTool = SurfaceTool.new() | |
surfaceTool.create_from(planeMesh, 0) | |
var arrayPlane = surfaceTool.commit() | |
var dataTool = MeshDataTool.new() | |
dataTool.create_from_surface(arrayPlane, 0) | |
for i in range(dataTool.get_vertex_count()): | |
var vertex = dataTool.get_vertex(i) | |
vertex.y = _noise.get_noise_3d(vertex.x, vertex.y, vertex.z) * yScale | |
dataTool.set_vertex(i, vertex) | |
for i in range(arrayPlane.get_surface_count()): | |
arrayPlane.clear_surfaces() | |
dataTool.commit_to_surface(arrayPlane) | |
# Generate with SurfaceTool | |
surfaceTool.begin(Mesh.PRIMITIVE_TRIANGLES) | |
surfaceTool.create_from(arrayPlane, 0) | |
surfaceTool.generate_normals() | |
# Create new MeshInstance3D | |
var meshInstance = MeshInstance3D.new() | |
meshInstance.mesh = surfaceTool.commit() | |
# If we're in the editor, add the mesh to the scene tree | |
if Engine.is_editor_hint(): | |
self.add_child(meshInstance) | |
meshInstance.owner = self.get_tree().edited_scene_root | |
# Set the material | |
meshInstance.mesh.surface_set_material(0, groundMaterial) | |
func _ready() -> void: | |
# If we're in the editor, generate the terrain | |
if Engine.is_editor_hint(): | |
generateTerrain() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment