Adds basic camp generation and placement
Adds basic camp generation and placement logic to the map generation process. It attempts to place the camp in a valid location, avoiding paths and water bodies. It also sets the player's spawn point to the center of the generated camp, including some basic camp props like a tent, campfire, and bed. Additionally, vegetation spawning is now dependent on the `should_spawn_*` methods of the `CellDataResource`, allowing more control over what spawns where.
This commit is contained in:
parent
3959333534
commit
a1efaf6294
8 changed files with 402 additions and 125 deletions
|
|
@ -5,13 +5,134 @@ extends Resource
|
|||
@export var x: int = 0
|
||||
@export var z: int = 0
|
||||
|
||||
@export var vegetation_density: float = 0.5
|
||||
@export var ground_compaction: float = 0.0
|
||||
@export var water: float = 0
|
||||
@export var moisture_level: float = 0.6
|
||||
# Core cell type properties - these will trigger automatic updates
|
||||
@export var camp: bool = false: set = set_camp
|
||||
@export var path: bool = false: set = set_trail
|
||||
@export var water: bool = false: set = set_water
|
||||
|
||||
# Dependent properties that get automatically managed
|
||||
@export var vegetation_density: float = 0.5: set = set_vegetation_density
|
||||
@export var ground_compaction: float = 0.0: set = set_ground_compaction
|
||||
@export var moisture_level: float = 0.6
|
||||
@export var trees: Array = []
|
||||
|
||||
# Internal flag to prevent infinite recursion during initialization
|
||||
var _initializing: bool = false
|
||||
|
||||
func _init():
|
||||
_initializing = true
|
||||
# Set default values
|
||||
vegetation_density = 0.5
|
||||
ground_compaction = 0.0
|
||||
camp = false
|
||||
path = false
|
||||
water = false
|
||||
_initializing = false
|
||||
|
||||
func set_camp(value: bool):
|
||||
camp = value
|
||||
if not _initializing:
|
||||
_update_dependent_properties()
|
||||
|
||||
## Using a different name due to Resource using the function set_path in Godot
|
||||
func set_trail(value: bool):
|
||||
path = value
|
||||
if not _initializing:
|
||||
_update_dependent_properties()
|
||||
|
||||
func set_water(value: bool):
|
||||
water = value
|
||||
if not _initializing:
|
||||
_update_dependent_properties()
|
||||
|
||||
func set_vegetation_density(value: float):
|
||||
# Only allow manual setting if not a special cell type
|
||||
if _initializing or not (camp or path or water):
|
||||
vegetation_density = value
|
||||
else:
|
||||
vegetation_density = 0.0
|
||||
|
||||
func set_ground_compaction(value: float):
|
||||
# Camp always has maximum compaction, others can be set manually
|
||||
if camp:
|
||||
ground_compaction = 1.0
|
||||
elif not _initializing:
|
||||
ground_compaction = value
|
||||
|
||||
func _update_dependent_properties():
|
||||
# Update vegetation density - always 0 for camp, path, or water
|
||||
if camp or path or water:
|
||||
vegetation_density = 0.0
|
||||
|
||||
# Update ground compaction - camp always has maximum compaction
|
||||
if camp:
|
||||
ground_compaction = 1.0
|
||||
|
||||
# Utility function to set cell type and ensure only one is active
|
||||
func set_cell_type(cell_type: String):
|
||||
_initializing = true
|
||||
|
||||
# Clear all cell types first
|
||||
camp = false
|
||||
path = false
|
||||
water = false
|
||||
|
||||
# Set the specified type
|
||||
match cell_type.to_lower():
|
||||
"camp":
|
||||
camp = true
|
||||
"path":
|
||||
path = true
|
||||
"water":
|
||||
water = true
|
||||
"terrain", "normal", "":
|
||||
pass # Leave all false for normal terrain
|
||||
_:
|
||||
push_warning("Unknown cell type: " + cell_type)
|
||||
|
||||
_initializing = false
|
||||
_update_dependent_properties()
|
||||
|
||||
# Utility function to get the primary cell type
|
||||
func get_cell_type() -> String:
|
||||
if camp:
|
||||
return "camp"
|
||||
elif path:
|
||||
return "path"
|
||||
elif water:
|
||||
return "water"
|
||||
else:
|
||||
return "terrain"
|
||||
|
||||
# Check if this is a special cell type (not normal terrain)
|
||||
func is_special_cell() -> bool:
|
||||
return camp or path or water
|
||||
|
||||
func add_trees(tree: TreeDataResource, qty: int) -> void:
|
||||
for i in qty:
|
||||
trees.append(tree)
|
||||
for i in qty:
|
||||
trees.append(tree)
|
||||
|
||||
# Override vegetation density and ground compaction for special cases
|
||||
func force_set_vegetation_density(value: float):
|
||||
# Allows bypassing the automatic management if absolutely needed
|
||||
vegetation_density = value
|
||||
|
||||
func force_set_ground_compaction(value: float):
|
||||
# Allows bypassing the automatic management if absolutely needed
|
||||
ground_compaction = value
|
||||
|
||||
func should_spawn_trees() -> bool:
|
||||
# Only spawn trees if this is not a special cell type
|
||||
return not (camp or path or water) and vegetation_density > 0.0
|
||||
|
||||
func should_spawn_grass() -> bool:
|
||||
# Grass can spawn in any terrain cell, but not in special cells
|
||||
return not (camp or path or water) and vegetation_density > 0.0
|
||||
|
||||
func should_spawn_bushes() -> bool:
|
||||
# Bushes can spawn in any terrain cell, but not in special cells
|
||||
return not (camp or path or water) and vegetation_density > 0.1
|
||||
|
||||
func should_spawn_flowers() -> bool:
|
||||
# Flowers can spawn in any terrain cell, but not in special cells
|
||||
return not (camp or path or water) and vegetation_density > 0.1
|
||||
|
|
@ -14,41 +14,15 @@ var map_data: Array = Global.map_data
|
|||
## Density 0.1 to 0.6 - add bushes, varying quantity TBD
|
||||
## Density 0.1 to 0.4 - add flowers, varying quantity TBD
|
||||
|
||||
# Weighted random selection based on tree chances
|
||||
static func select_weighted_tree(tree_preferences: Dictionary):
|
||||
if tree_preferences.is_empty():
|
||||
return null
|
||||
|
||||
# Calculate total weight
|
||||
var total_weight = 0.0
|
||||
for tree_name in tree_preferences.keys():
|
||||
total_weight += tree_preferences[tree_name]["chance"]
|
||||
|
||||
if total_weight <= 0.0:
|
||||
return null
|
||||
|
||||
# Generate random number between 0 and total_weight
|
||||
var random_value = randf() * total_weight
|
||||
|
||||
# Find which tree this random value corresponds to
|
||||
var cumulative_weight = 0.0
|
||||
for tree_name in tree_preferences.keys():
|
||||
cumulative_weight += tree_preferences[tree_name]["chance"]
|
||||
if random_value <= cumulative_weight:
|
||||
return tree_preferences[tree_name]["resource"]
|
||||
|
||||
# Fallback (shouldn't happen, but just in case)
|
||||
var first_key = tree_preferences.keys()[0]
|
||||
return tree_preferences[first_key]["resource"]
|
||||
|
||||
# Pre-calculate tree distribution for the cell
|
||||
static func generate_cell_with_distribution(x: int, z: int, density: float, path: bool = false, water: bool = false):
|
||||
static func generate_cell_with_distribution(x: int, z: int, density: float, path: bool = false, water: bool = false, camp: bool = false):
|
||||
var cell_data = CellDataResource.new()
|
||||
cell_data.x = x
|
||||
cell_data.z = z
|
||||
cell_data.vegetation_density = density
|
||||
cell_data.camp = camp
|
||||
|
||||
if not (path or water):
|
||||
if not (path or water or camp):
|
||||
if density >= 0.6:
|
||||
var tree_preferences = BiomeData.calculate_tree_probabilities(x, z)
|
||||
var tree_distribution = calculate_tree_distribution(tree_preferences, density)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ var map_height: int = Global.map_height
|
|||
@export var min_branch_length_ratio: float = 0.2 # Minimum branch length as ratio of main path
|
||||
@export var max_branch_length_ratio: float = 0.4 # Maximum branch length as ratio of main path
|
||||
|
||||
# Camp generation settings
|
||||
@export var camp_size: int = 2 # Size of the camp (camp_size x camp_size)
|
||||
@export var camp_placement_attempts: int = 100 # Max attempts to place the camp
|
||||
@export var camp_buffer_zone: int = 1 # Minimum distance from camp to other features
|
||||
|
||||
# Water generation settings
|
||||
@export var num_water_bodies: int = 5 # Number of water bodies to generate
|
||||
@export var min_water_size: int = 15 # Minimum radius of water bodies
|
||||
|
|
@ -32,6 +37,11 @@ var map: Array = []
|
|||
var map_data: Array = []
|
||||
var path_data: Array = [] # Separate array to track paths
|
||||
var water_data: Array = [] # Separate array to track water bodies
|
||||
var camp_data: Array = [] # Separate array to track camp
|
||||
|
||||
# Camp position storage
|
||||
var camp_position: Vector2 = Vector2(-1, -1) # Top-left corner of the camp
|
||||
var camp_center: Vector2 = Vector2(-1, -1) # Center of the camp
|
||||
|
||||
# New data structures for enhanced path generation
|
||||
var path_segments: Array = [] # Store all path segments for better branching
|
||||
|
|
@ -39,22 +49,27 @@ var path_id_counter: int = 0
|
|||
|
||||
func _ready():
|
||||
generate_map()
|
||||
generate_camp()
|
||||
generate_paths()
|
||||
generate_water_bodies()
|
||||
BiomeGenerationClass.generate_environment_maps(map_width, map_height)
|
||||
generate_final_map_data()
|
||||
|
||||
|
||||
#if export_image:
|
||||
# export_map_as_image()
|
||||
if export_image:
|
||||
export_map_as_image()
|
||||
|
||||
func generate_final_map_data():
|
||||
var objects_before = Performance.get_monitor(Performance.OBJECT_COUNT)
|
||||
|
||||
for y in range(map_height):
|
||||
for x in range(map_width):
|
||||
MapPopulationClass.generate_cell_with_distribution(x, y, map_data[y][x], is_path_at(x, y), is_water_at(x, y))
|
||||
MapPopulationClass.generate_cell_with_distribution(x, y, map_data[y][x], is_path_at(x, y), is_water_at(x, y), is_camp_at(x, y))
|
||||
|
||||
Log.pr(camp_center)
|
||||
Global.spawn_point = Vector3(camp_position.x * 2, 0, camp_position.y * 2) # Set spawn point to camp center
|
||||
Log.pr(Global.spawn_point)
|
||||
|
||||
# Check immediately after
|
||||
await get_tree().process_frame
|
||||
var objects_after = Performance.get_monitor(Performance.OBJECT_COUNT)
|
||||
|
|
@ -86,14 +101,17 @@ func generate_map():
|
|||
map_data.resize(map_height)
|
||||
path_data.resize(map_height)
|
||||
water_data.resize(map_height)
|
||||
camp_data.resize(map_height)
|
||||
|
||||
for y in range(map_height):
|
||||
map_data[y] = []
|
||||
path_data[y] = []
|
||||
water_data[y] = []
|
||||
camp_data[y] = []
|
||||
map_data[y].resize(map_width)
|
||||
path_data[y].resize(map_width)
|
||||
water_data[y].resize(map_width)
|
||||
camp_data[y].resize(map_width)
|
||||
for x in range(map_width):
|
||||
# Get noise value (-1 to 1) and normalize to (0 to 1)
|
||||
var noise_value = noise.get_noise_2d(x, y)
|
||||
|
|
@ -101,7 +119,66 @@ func generate_map():
|
|||
map_data[y][x] = round(normalized_value * 10.0) / 10.0
|
||||
path_data[y][x] = false # Initialize path data
|
||||
water_data[y][x] = false # Initialize water data
|
||||
camp_data[y][x] = false # Initialize camp data
|
||||
|
||||
func generate_camp():
|
||||
print("Generating camp...")
|
||||
|
||||
var attempts = 0
|
||||
var placed = false
|
||||
|
||||
while attempts < camp_placement_attempts and not placed:
|
||||
# Random position with margin to ensure camp fits within map bounds
|
||||
var margin = camp_size + camp_buffer_zone
|
||||
var camp_x = randi_range(margin, map_width - margin - camp_size)
|
||||
var camp_y = randi_range(margin, map_height - margin - camp_size)
|
||||
|
||||
# Check if this location is valid for camp placement
|
||||
if is_valid_camp_location(camp_x, camp_y):
|
||||
place_camp(camp_x, camp_y)
|
||||
placed = true
|
||||
print("Camp placed at (", camp_x, ",", camp_y, ") with size ", camp_size, "x", camp_size)
|
||||
|
||||
attempts += 1
|
||||
|
||||
if not placed:
|
||||
print("Could not place camp after ", camp_placement_attempts, " attempts")
|
||||
# Fallback: place camp in the center of the map
|
||||
var fallback_x = (map_width - camp_size) / 2
|
||||
var fallback_y = (map_height - camp_size) / 2
|
||||
place_camp(fallback_x, fallback_y)
|
||||
print("Camp placed at fallback location (", fallback_x, ",", fallback_y, ")")
|
||||
|
||||
func is_valid_camp_location(camp_x: int, camp_y: int) -> bool:
|
||||
# Check if the camp area and buffer zone are clear
|
||||
var check_size = camp_size + (camp_buffer_zone * 2)
|
||||
var start_x = camp_x - camp_buffer_zone
|
||||
var start_y = camp_y - camp_buffer_zone
|
||||
|
||||
for y in range(start_y, start_y + check_size):
|
||||
for x in range(start_x, start_x + check_size):
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
# For now, just check if we're within map bounds
|
||||
# Later we'll prevent paths and water from being placed here
|
||||
continue
|
||||
else:
|
||||
# Outside map bounds
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
func place_camp(camp_x: int, camp_y: int):
|
||||
# Store camp position
|
||||
camp_position = Vector2(camp_x, camp_y)
|
||||
camp_center = Vector2(camp_x + camp_size / 2.0, camp_y + camp_size / 2.0)
|
||||
|
||||
Log.pr("Placing camp at position: ", camp_position, " with center: ", camp_center)
|
||||
|
||||
# Mark camp area in camp_data
|
||||
for y in range(camp_y, camp_y + camp_size):
|
||||
for x in range(camp_x, camp_x + camp_size):
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
camp_data[y][x] = true
|
||||
|
||||
func generate_paths():
|
||||
print("Generating natural paths with branching...")
|
||||
|
|
@ -416,6 +493,10 @@ func evaluate_catmull_rom_spline(points: Array, t: float) -> Vector2:
|
|||
return result
|
||||
|
||||
func place_organic_path_segment(x: int, y: int, path_id: int, is_main_path: bool = false):
|
||||
# Don't place paths in camp area
|
||||
if is_camp_at(x, y):
|
||||
return
|
||||
|
||||
# Base path placement - always place the center tile
|
||||
path_data[y][x] = true
|
||||
|
||||
|
|
@ -440,6 +521,10 @@ func place_organic_path_segment(x: int, y: int, path_id: int, is_main_path: bool
|
|||
var ny = y + dy
|
||||
|
||||
if nx >= 0 and nx < map_width and ny >= 0 and ny < map_height:
|
||||
# Don't place paths in camp area
|
||||
if is_camp_at(nx, ny):
|
||||
continue
|
||||
|
||||
# Use noise to create organic edges
|
||||
var edge_noise = width_noise.get_noise_2d(nx, ny)
|
||||
var edge_chance = 1.0 - (distance / float(path_width))
|
||||
|
|
@ -483,7 +568,9 @@ func smooth_paths():
|
|||
var interp_x = x + int(float(dx * step) / float(steps))
|
||||
var interp_y = y + int(float(dy * step) / float(steps))
|
||||
if interp_x >= 0 and interp_x < map_width and interp_y >= 0 and interp_y < map_height:
|
||||
new_path_data[interp_y][interp_x] = true
|
||||
# Don't place paths in camp area
|
||||
if not is_camp_at(interp_x, interp_y):
|
||||
new_path_data[interp_y][interp_x] = true
|
||||
connected = true
|
||||
break
|
||||
if connected:
|
||||
|
|
@ -509,9 +596,9 @@ func generate_single_water_body(body_index: int):
|
|||
var center_y = randi_range(max_water_size, map_height - max_water_size)
|
||||
|
||||
# Random size within configured range
|
||||
var water_radius = randi_range(min_water_size, max_water_size)
|
||||
var water_radius = randi_range(min_water_size, max_water_size) - attempts
|
||||
|
||||
# Check if this location is valid (no paths in the area)
|
||||
# Check if this location is valid (no paths or camp in the area)
|
||||
if is_valid_water_location(center_x, center_y, water_radius):
|
||||
place_water_body(center_x, center_y, water_radius, body_index)
|
||||
placed = true
|
||||
|
|
@ -523,7 +610,7 @@ func generate_single_water_body(body_index: int):
|
|||
print("Could not place water body ", body_index + 1, " after ", water_placement_attempts, " attempts")
|
||||
|
||||
func is_valid_water_location(center_x: int, center_y: int, radius: int) -> bool:
|
||||
# Check if any paths would be intersected by this water body
|
||||
# Check if any paths or camp would be intersected by this water body
|
||||
# Use a slightly larger radius for safety buffer
|
||||
var safety_buffer = 2
|
||||
var check_radius = radius + safety_buffer
|
||||
|
|
@ -537,6 +624,12 @@ func is_valid_water_location(center_x: int, center_y: int, radius: int) -> bool:
|
|||
if distance <= check_radius:
|
||||
return false
|
||||
|
||||
# Check if this position has camp
|
||||
if camp_data[y][x]:
|
||||
var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
|
||||
if distance <= check_radius:
|
||||
return false
|
||||
|
||||
# Also check if there's already water here (prevent overlap)
|
||||
if water_data[y][x]:
|
||||
var distance = sqrt(pow(x - center_x, 2) + pow(y - center_y, 2))
|
||||
|
|
@ -556,7 +649,7 @@ func place_water_body(center_x: int, center_y: int, radius: int, body_index: int
|
|||
for y in range(center_y - radius - 2, center_y + radius + 3):
|
||||
for x in range(center_x - radius - 2, center_x + radius + 3):
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
if path_data[y][x]: # Don't place water on paths
|
||||
if path_data[y][x] or camp_data[y][x]: # Don't place water on paths or camp
|
||||
continue
|
||||
|
||||
# Calculate distance from center
|
||||
|
|
@ -574,7 +667,7 @@ func place_water_body(center_x: int, center_y: int, radius: int, body_index: int
|
|||
func print_visual_map_to_console():
|
||||
print("")
|
||||
print("==================================================")
|
||||
print("VISUAL MAP WITH BRANCHING PATHS AND DEAD ENDS")
|
||||
print("VISUAL MAP WITH BRANCHING PATHS, DEAD ENDS, AND CAMP")
|
||||
print("==================================================")
|
||||
print("Path Statistics:")
|
||||
print("- Total path segments: ", path_segments.size())
|
||||
|
|
@ -585,11 +678,16 @@ func print_visual_map_to_console():
|
|||
print("- Branches: ", branches.size())
|
||||
print("- Dead ends: ", dead_ends.size())
|
||||
print("")
|
||||
print("Camp Statistics:")
|
||||
print("- Camp position: (", camp_position.x, ",", camp_position.y, ")")
|
||||
print("- Camp center: (", camp_center.x, ",", camp_center.y, ")")
|
||||
print("- Camp size: ", camp_size, "x", camp_size)
|
||||
print("")
|
||||
|
||||
print_block_map_with_paths_safe()
|
||||
|
||||
func print_block_map_with_paths_safe():
|
||||
print("Block character map with paths (X = paths, W = water):")
|
||||
print("Block character map with paths (X = paths, W = water, C = camp):")
|
||||
print("")
|
||||
|
||||
# Print in smaller chunks
|
||||
|
|
@ -600,8 +698,10 @@ func print_block_map_with_paths_safe():
|
|||
for y in range(chunk_start, chunk_end):
|
||||
var row_string = ""
|
||||
for x in range(map_width):
|
||||
# Check priority: paths first, then water, then terrain
|
||||
if path_data[y][x]:
|
||||
# Check priority: camp first, then paths, then water, then terrain
|
||||
if camp_data[y][x]:
|
||||
row_string += "C"
|
||||
elif path_data[y][x]:
|
||||
row_string += "X"
|
||||
elif water_data[y][x]:
|
||||
row_string += "W"
|
||||
|
|
@ -639,19 +739,35 @@ func is_water_at(x: int, y: int) -> bool:
|
|||
return water_data[y][x]
|
||||
return false
|
||||
|
||||
func is_camp_at(x: int, y: int) -> bool:
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
return camp_data[y][x]
|
||||
return false
|
||||
|
||||
func get_terrain_at(x: int, y: int) -> float:
|
||||
if x >= 0 and x < map_width and y >= 0 and y < map_height:
|
||||
return map_data[y][x]
|
||||
return 0.0
|
||||
|
||||
func get_tile_type_at(x: int, y: int) -> String:
|
||||
if is_path_at(x, y):
|
||||
if is_camp_at(x, y):
|
||||
return "camp"
|
||||
elif is_path_at(x, y):
|
||||
return "path"
|
||||
elif is_water_at(x, y):
|
||||
return "water"
|
||||
else:
|
||||
return "terrain"
|
||||
|
||||
func get_camp_position() -> Vector2:
|
||||
return camp_position
|
||||
|
||||
func get_camp_center() -> Vector2:
|
||||
return camp_center
|
||||
|
||||
func get_camp_size() -> int:
|
||||
return camp_size
|
||||
|
||||
func get_path_segments() -> Array:
|
||||
return path_segments
|
||||
|
||||
|
|
@ -687,6 +803,26 @@ func export_map_as_image():
|
|||
var image_y = map_y * tile_size + pixel_y
|
||||
image.set_pixel(image_x, image_y, color)
|
||||
|
||||
# Draw camp as a red dot at the center
|
||||
if camp_position.x >= 0 and camp_position.y >= 0:
|
||||
var camp_center_pixel_x = int(camp_center.x * tile_size)
|
||||
var camp_center_pixel_y = int(camp_center.y * tile_size)
|
||||
|
||||
# Draw a red dot (3x3 pixels minimum, scaled with tile size)
|
||||
var dot_size = 20
|
||||
var half_dot = dot_size / 2
|
||||
|
||||
for dy in range(-half_dot, half_dot + 1):
|
||||
for dx in range(-half_dot, half_dot + 1):
|
||||
var pixel_x = camp_center_pixel_x + dx
|
||||
var pixel_y = camp_center_pixel_y + dy
|
||||
|
||||
# Make sure we're within image bounds
|
||||
if pixel_x >= 0 and pixel_x < image_width and pixel_y >= 0 and pixel_y < image_height:
|
||||
# Create a circular dot
|
||||
if dx * dx + dy * dy <= half_dot * half_dot:
|
||||
image.set_pixel(pixel_x, pixel_y, Color.RED)
|
||||
|
||||
# Save the image - you can choose different locations:
|
||||
var file_path = "user://" + image_filename
|
||||
|
||||
|
|
@ -700,11 +836,14 @@ func export_map_as_image():
|
|||
print("Image dimensions: ", image_width, "x", image_height, " pixels")
|
||||
print("Map dimensions: ", map_width, "x", map_height, " tiles")
|
||||
print("Tile size: ", tile_size, "x", tile_size, " pixels per tile")
|
||||
print("Camp represented as red dot at center: (", camp_center.x, ",", camp_center.y, ")")
|
||||
else:
|
||||
print("Error saving image: ", error)
|
||||
|
||||
func get_tile_color(x: int, y: int) -> Color:
|
||||
if is_path_at(x, y):
|
||||
if is_camp_at(x, y):
|
||||
return Color(0.4, 0.7, 0.3)
|
||||
elif is_path_at(x, y):
|
||||
# Brown color for paths
|
||||
return Color(0.6, 0.4, 0.2) # Brown
|
||||
elif is_water_at(x, y):
|
||||
|
|
@ -720,4 +859,4 @@ func get_tile_color(x: int, y: int) -> Color:
|
|||
var light_green = Color(0.4, 0.7, 0.3) # Light green
|
||||
|
||||
# Interpolate between dark and light green based on terrain value
|
||||
return dark_green.lerp(light_green, terrain_value)
|
||||
return dark_green.lerp(light_green, terrain_value)
|
||||
Loading…
Add table
Add a link
Reference in a new issue