From ea5006e8a2fcd9ee9afde97660f3533e394b46ee Mon Sep 17 00:00:00 2001 From: Dan Baker Date: Sat, 28 Jun 2025 17:21:50 +0100 Subject: [PATCH] Improves tree and ground tile appearance Refactors tree spawning and rendering: - Adds seasonal color variations for trees via scripts. - Introduces `ColorStorage` to manage tree and grass colors - Removes unused code from tree spawning logic. - Adjusts ground tile color for better visual appeal. - Hides debug text on ground tiles. --- Entities/GroundTile/GroundTile.tscn | 4 +- Entities/GroundTile/ground_tile.gd | 3 + Entities/GroundTile/scripts/trees.gd | 41 +----- Entities/Tree/assets/Textures/texture.png | 3 + .../Tree/assets/Textures/texture.png.import | 35 ++++++ .../assets/{temp => }/tree_pineDefaultA.glb | Bin .../{temp => }/tree_pineDefaultA.glb.import | 6 +- Entities/Tree/resources/TreeData.tres | 50 ++++++-- Entities/Tree/resources/TreeDataResource.gd | 32 ++++- Entities/Tree/scripts/color_default.gd | 108 ++++++++++++++++ Entities/Tree/scripts/color_default.gd.uid | 1 + Entities/Tree/scripts/color_tree_birch.gd | 19 +++ Entities/Tree/scripts/color_tree_birch.gd.uid | 1 + Entities/Tree/scripts/color_tree_elder.gd | 19 +++ Entities/Tree/scripts/color_tree_elder.gd.uid | 1 + Entities/Tree/scripts/color_tree_oak.gd | 19 +++ Entities/Tree/scripts/color_tree_oak.gd.uid | 1 + Entities/Tree/scripts/color_tree_pine.gd | 19 +++ Entities/Tree/scripts/color_tree_pine.gd.uid | 1 + Entities/Tree/scripts/tree.gd | 9 +- Utilities/ColorStorage/ColorStorage.gd | 119 ++++++++++++++++++ Utilities/ColorStorage/ColorStorage.gd.uid | 1 + falloff.gdshader | 10 ++ falloff.gdshader.uid | 1 + project.godot | 1 + stages/Test3D/GrassMaterialOverride.tres | 9 +- stages/Test3D/Test3d.tscn | 4 +- .../assets/stylizedGrassMeshes/grass.res | Bin 3460 -> 3879 bytes 28 files changed, 454 insertions(+), 63 deletions(-) create mode 100644 Entities/Tree/assets/Textures/texture.png create mode 100644 Entities/Tree/assets/Textures/texture.png.import rename Entities/Tree/assets/{temp => }/tree_pineDefaultA.glb (100%) rename Entities/Tree/assets/{temp => }/tree_pineDefaultA.glb.import (73%) create mode 100644 Entities/Tree/scripts/color_default.gd create mode 100644 Entities/Tree/scripts/color_default.gd.uid create mode 100644 Entities/Tree/scripts/color_tree_birch.gd create mode 100644 Entities/Tree/scripts/color_tree_birch.gd.uid create mode 100644 Entities/Tree/scripts/color_tree_elder.gd create mode 100644 Entities/Tree/scripts/color_tree_elder.gd.uid create mode 100644 Entities/Tree/scripts/color_tree_oak.gd create mode 100644 Entities/Tree/scripts/color_tree_oak.gd.uid create mode 100644 Entities/Tree/scripts/color_tree_pine.gd create mode 100644 Entities/Tree/scripts/color_tree_pine.gd.uid create mode 100644 Utilities/ColorStorage/ColorStorage.gd create mode 100644 Utilities/ColorStorage/ColorStorage.gd.uid create mode 100644 falloff.gdshader create mode 100644 falloff.gdshader.uid diff --git a/Entities/GroundTile/GroundTile.tscn b/Entities/GroundTile/GroundTile.tscn index 37e6239..cd4bf43 100644 --- a/Entities/GroundTile/GroundTile.tscn +++ b/Entities/GroundTile/GroundTile.tscn @@ -11,8 +11,7 @@ viewport_path = NodePath("DebugText/DebugTextViewport") [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_f37ob"] -albedo_color = Color(0.133333, 0.576471, 0.47451, 1) -metallic = 1.0 +albedo_color = Color(0.196078, 0.392157, 0.196078, 1) [sub_resource type="PlaneMesh" id="PlaneMesh_oqd8f"] @@ -30,6 +29,7 @@ flip_faces = true script = ExtResource("1_uwxqs") [node name="DebugText" type="Node3D" parent="."] +visible = false [node name="DebugTextViewport" type="SubViewport" parent="DebugText"] size = Vector2i(50, 50) diff --git a/Entities/GroundTile/ground_tile.gd b/Entities/GroundTile/ground_tile.gd index 2e67574..86f29b5 100644 --- a/Entities/GroundTile/ground_tile.gd +++ b/Entities/GroundTile/ground_tile.gd @@ -9,6 +9,9 @@ var cell_info: CellDataResource = null var spawners_ready: bool = false var cached_rng: RandomClass = null +# 326432 229379 +# Grass - 1a7761 1f6051 + func _ready() -> void: spawners_ready = true diff --git a/Entities/GroundTile/scripts/trees.gd b/Entities/GroundTile/scripts/trees.gd index 929b62b..9743906 100644 --- a/Entities/GroundTile/scripts/trees.gd +++ b/Entities/GroundTile/scripts/trees.gd @@ -65,50 +65,11 @@ func spawn_tree_at_position(pos: Vector3, tree_resource: TreeDataResource): # Pass the TreeDataResource to the Tree instance if tree_instance.has_method("set_tree_data"): tree_instance.set_tree_data(tree_resource) - elif tree_instance.has_method("setup_tree"): - tree_instance.setup_tree(tree_resource) else: - Log.pr("Tree instance doesn't have set_tree_data() or setup_tree() method") + Log.pr("Tree instance doesn't have set_tree_data() method") func is_position_valid(pos: Vector3) -> bool: for existing_pos in spawned_positions: if pos.distance_to(existing_pos) < min_distance: return false return true - -# Alternative method that shuffles trees for more random placement -func spawn_trees_shuffled(cell_info: CellDataResource): - if not cell_info: - return - - if not parent_ground_tile: - return - - # Clear existing trees - for child in get_children(): - child.free() - spawned_positions.clear() - - # Create a copy and shuffle for random placement order - var trees_to_spawn = cell_info.trees.duplicate() - trees_to_spawn.shuffle() - - var spawned_count = 0 - var attempts = 0 - var max_attempts = trees_to_spawn.size() * 10 - - for tree_resource in trees_to_spawn: - if attempts >= max_attempts: - Log.pr("Reached max attempts, could only spawn %d of %d trees" % [spawned_count, trees_to_spawn.size()]) - break - - var pos = get_random_position() - - if is_position_valid(pos): - spawn_tree_at_position(pos, tree_resource as TreeDataResource) - spawned_positions.append(pos) - spawned_count += 1 - - attempts += 1 - - Log.pr("Spawned %d of %d trees in cell (shuffled)" % [spawned_count, trees_to_spawn.size()]) diff --git a/Entities/Tree/assets/Textures/texture.png b/Entities/Tree/assets/Textures/texture.png new file mode 100644 index 0000000..5ba4aad --- /dev/null +++ b/Entities/Tree/assets/Textures/texture.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:919826bd383102db0e4fbbb85465903d92fb18a5f3710d014796b1bbba7829f0 +size 122084 diff --git a/Entities/Tree/assets/Textures/texture.png.import b/Entities/Tree/assets/Textures/texture.png.import new file mode 100644 index 0000000..a0eb094 --- /dev/null +++ b/Entities/Tree/assets/Textures/texture.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dhajcn6k04poe" +path.s3tc="res://.godot/imported/texture.png-27d352bfc378434578d288ba141a0d45.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://Entities/Tree/assets/Textures/texture.png" +dest_files=["res://.godot/imported/texture.png-27d352bfc378434578d288ba141a0d45.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/Entities/Tree/assets/temp/tree_pineDefaultA.glb b/Entities/Tree/assets/tree_pineDefaultA.glb similarity index 100% rename from Entities/Tree/assets/temp/tree_pineDefaultA.glb rename to Entities/Tree/assets/tree_pineDefaultA.glb diff --git a/Entities/Tree/assets/temp/tree_pineDefaultA.glb.import b/Entities/Tree/assets/tree_pineDefaultA.glb.import similarity index 73% rename from Entities/Tree/assets/temp/tree_pineDefaultA.glb.import rename to Entities/Tree/assets/tree_pineDefaultA.glb.import index 3af2477..c7da8e4 100644 --- a/Entities/Tree/assets/temp/tree_pineDefaultA.glb.import +++ b/Entities/Tree/assets/tree_pineDefaultA.glb.import @@ -4,12 +4,12 @@ importer="scene" importer_version=1 type="PackedScene" uid="uid://brabfdgw5vs1o" -path="res://.godot/imported/tree_pineDefaultA.glb-1f3fb96752d92654789d1e25d544e1b6.scn" +path="res://.godot/imported/tree_pineDefaultA.glb-5dac8705865b7821c88a664af5de372c.scn" [deps] -source_file="res://Entities/Tree/assets/temp/tree_pineDefaultA.glb" -dest_files=["res://.godot/imported/tree_pineDefaultA.glb-1f3fb96752d92654789d1e25d544e1b6.scn"] +source_file="res://Entities/Tree/assets/tree_pineDefaultA.glb" +dest_files=["res://.godot/imported/tree_pineDefaultA.glb-5dac8705865b7821c88a664af5de372c.scn"] [params] diff --git a/Entities/Tree/resources/TreeData.tres b/Entities/Tree/resources/TreeData.tres index 52dc4ae..64e9eb9 100644 --- a/Entities/Tree/resources/TreeData.tres +++ b/Entities/Tree/resources/TreeData.tres @@ -1,11 +1,15 @@ -[gd_resource type="Resource" script_class="TreeDataCollection" load_steps=11 format=3 uid="uid://buf5mtxc5f7o"] +[gd_resource type="Resource" script_class="TreeDataCollection" load_steps=15 format=3 uid="uid://buf5mtxc5f7o"] [ext_resource type="Script" uid="uid://dj4dhsd0m0bvd" path="res://Entities/Tree/resources/TreeDataCollection.gd" id="1_7y4l8"] [ext_resource type="Script" uid="uid://cdudelqysppwl" path="res://Entities/Tree/resources/TreeDataResource.gd" id="2_4yk3p"] [ext_resource type="PackedScene" uid="uid://g74ar24o4s1" path="res://Entities/Tree/assets/tree_oak.glb" id="3_4yk3p"] -[ext_resource type="PackedScene" uid="uid://bwhpbjdyl577e" path="res://Entities/Tree/assets/tree.glb" id="4_4wxxs"] +[ext_resource type="Script" uid="uid://bjsj2j01nmnfq" path="res://Entities/Tree/scripts/color_tree_oak.gd" id="3_itox7"] [ext_resource type="PackedScene" uid="uid://besaean4w1n83" path="res://Entities/Tree/assets/tree_birch.glb" id="5_35hba"] +[ext_resource type="Script" uid="uid://de77441cemrqe" path="res://Entities/Tree/scripts/color_tree_pine.gd" id="5_cyc7o"] [ext_resource type="PackedScene" uid="uid://b3lc1nbuv5ol8" path="res://Entities/Tree/assets/tree_detailed.glb" id="6_ibfiw"] +[ext_resource type="PackedScene" uid="uid://brabfdgw5vs1o" path="res://Entities/Tree/assets/tree_pineDefaultA.glb" id="6_itox7"] +[ext_resource type="Script" uid="uid://dk04iuaocilih" path="res://Entities/Tree/scripts/color_tree_birch.gd" id="7_cyc7o"] +[ext_resource type="Script" uid="uid://3flh70jpmq2w" path="res://Entities/Tree/scripts/color_tree_elder.gd" id="9_dydvk"] [sub_resource type="Resource" id="Resource_7y4l8"] script = ExtResource("2_4yk3p") @@ -17,28 +21,44 @@ moisture_range = Vector2(0.3, 0.8) elevation_range = Vector2(0.1, 0.5) max_height = 15.0 growth_time = 120.0 -spawn_probability = 0.1 spread_radius = 5.0 seasonal_models = Array[PackedScene]([]) drops_leaves = true leaf_color_variations = Array[Color]([]) +color_script = ExtResource("3_itox7") +spring_leaf_color = Color(0.419608, 0.556863, 0.137255, 1) +summer_leaf_color = Color(0.419608, 0.556863, 0.137255, 1) +autumn_leaf_color = Color(0.624662, 0.302547, 1.92523e-07, 1) +winter_leaf_color = Color(0.784314, 0.823529, 0.745098, 1) +spring_trunk_color = Color(0.627451, 0.321569, 0.176471, 1) +summer_trunk_color = Color(0.627451, 0.321569, 0.176471, 1) +autumn_trunk_color = Color(0.4, 0.2, 0.1, 1) +winter_trunk_color = Color(0.3, 0.2, 0.1, 1) metadata/_custom_type_script = "uid://cdudelqysppwl" [sub_resource type="Resource" id="Resource_35hba"] script = ExtResource("2_4yk3p") tree_name = "Pine" -model = ExtResource("4_4wxxs") +model = ExtResource("6_itox7") growth_stages = Array[PackedScene]([]) temperature_range = Vector2(0.1, 0.6) moisture_range = Vector2(0.2, 0.6) elevation_range = Vector2(0.7, 1) max_height = 15.0 growth_time = 120.0 -spawn_probability = 0.1 spread_radius = 5.0 seasonal_models = Array[PackedScene]([]) drops_leaves = true leaf_color_variations = Array[Color]([]) +color_script = ExtResource("5_cyc7o") +spring_leaf_color = Color(0.196078, 0.501961, 0.196078, 1) +summer_leaf_color = Color(0.00392157, 0.196078, 0.12549, 1) +autumn_leaf_color = Color(0.133333, 0.376471, 0.168627, 1) +winter_leaf_color = Color(0.266667, 0.376471, 0.298039, 1) +spring_trunk_color = Color(0.396078, 0.262745, 0.129412, 1) +summer_trunk_color = Color(0.396078, 0.262745, 0.129412, 1) +autumn_trunk_color = Color(0.396078, 0.262745, 0.129412, 1) +winter_trunk_color = Color(0.313726, 0.207843, 0.101961, 1) metadata/_custom_type_script = "uid://cdudelqysppwl" [sub_resource type="Resource" id="Resource_4wxxs"] @@ -51,11 +71,19 @@ moisture_range = Vector2(0.5, 0.6) elevation_range = Vector2(0.1, 0.5) max_height = 15.0 growth_time = 120.0 -spawn_probability = 0.1 spread_radius = 5.0 seasonal_models = Array[PackedScene]([]) drops_leaves = true leaf_color_variations = Array[Color]([]) +color_script = ExtResource("7_cyc7o") +spring_leaf_color = Color(0.678431, 0.933333, 0.466667, 1) +summer_leaf_color = Color(0.603922, 0.803922, 0.196078, 1) +autumn_leaf_color = Color(1, 0.843137, 0, 1) +winter_leaf_color = Color(0.898039, 0.917647, 0.823529, 1) +spring_trunk_color = Color(0.960784, 0.960784, 0.862745, 1) +summer_trunk_color = Color(0.960784, 0.960784, 0.862745, 1) +autumn_trunk_color = Color(0.960784, 0.960784, 0.862745, 1) +winter_trunk_color = Color(0.862745, 0.862745, 0.784314, 1) metadata/_custom_type_script = "uid://cdudelqysppwl" [sub_resource type="Resource" id="Resource_54eyh"] @@ -68,11 +96,19 @@ moisture_range = Vector2(0.7, 1) elevation_range = Vector2(0, 0.2) max_height = 15.0 growth_time = 120.0 -spawn_probability = 0.1 spread_radius = 5.0 seasonal_models = Array[PackedScene]([]) drops_leaves = true leaf_color_variations = Array[Color]([]) +color_script = ExtResource("9_dydvk") +spring_leaf_color = Color(0.564706, 0.933333, 0.564706, 1) +summer_leaf_color = Color(0.196078, 0.803922, 0.196078, 1) +autumn_leaf_color = Color(0.545098, 0.270588, 0.0745098, 1) +winter_leaf_color = Color(0.627451, 0.666667, 0.627451, 1) +spring_trunk_color = Color(0.411765, 0.411765, 0.411765, 1) +summer_trunk_color = Color(0.411765, 0.411765, 0.411765, 1) +autumn_trunk_color = Color(0.411765, 0.411765, 0.411765, 1) +winter_trunk_color = Color(0.329412, 0.329412, 0.329412, 1) metadata/_custom_type_script = "uid://cdudelqysppwl" [resource] diff --git a/Entities/Tree/resources/TreeDataResource.gd b/Entities/Tree/resources/TreeDataResource.gd index abd2411..8d50ef8 100644 --- a/Entities/Tree/resources/TreeDataResource.gd +++ b/Entities/Tree/resources/TreeDataResource.gd @@ -14,10 +14,40 @@ extends Resource @export_group("Growth Properties") @export var max_height: float = 15.0 @export var growth_time: float = 120.0 # Seconds to full growth -@export var spawn_probability: float = 0.1 @export var spread_radius: float = 5.0 @export_group("Seasonal Behavior") @export var seasonal_models: Array[PackedScene] = [] # Spring, Summer, Fall, Winter @export var drops_leaves: bool = true @export var leaf_color_variations: Array[Color] = [] + +@export_group("Tree Color Management") +@export var color_script: GDScript = null + +@export_group("Leaf Colors") +@export var spring_leaf_color: Color = Color(0.0, 1.0, 0.0) # Green +@export var summer_leaf_color: Color = Color(0.0, 0.5, 0.0) # Darker green +@export var autumn_leaf_color: Color = Color(1.0, 0.5, 0.0) # Orange +@export var winter_leaf_color: Color = Color(0.5, 0.5, 0.5) # Gray/Brown + +@export_group("Trunk Colors") +@export var spring_trunk_color: Color = Color(0.6, 0.4, 0.2) # Brown +@export var summer_trunk_color: Color = Color(0.5, 0.3, 0.1) # Darker brown +@export var autumn_trunk_color: Color = Color(0.4, 0.2, 0.1) # Darker brown +@export var winter_trunk_color: Color = Color(0.3, 0.2, 0.1) # Very dark brown + +func apply_seasonal_colors(instance_model: Node3D, season: String = "summer") -> void: + if color_script == null: + return + + var script_instance = color_script.new() + + # Pass the tree name and season to the color script + if script_instance.has_method("set_tree_info"): + script_instance.set_tree_info(tree_name, season) + + + if script_instance.has_method("set_leaf_color"): + script_instance.set_leaf_color(instance_model) + if script_instance.has_method("set_trunk_color"): + script_instance.set_trunk_color(instance_model) \ No newline at end of file diff --git a/Entities/Tree/scripts/color_default.gd b/Entities/Tree/scripts/color_default.gd new file mode 100644 index 0000000..b91a5ab --- /dev/null +++ b/Entities/Tree/scripts/color_default.gd @@ -0,0 +1,108 @@ +class_name TreeColorDefault +extends RefCounted + +var tree_name: String = "" +var current_season: String = "summer" +var mesh_instances: Array[MeshInstance3D] = [] + +func set_tree_info(name: String, season: String): + tree_name = name + current_season = season + +func set_leaf_color(model_instance: Node3D): + #Log.pr("Setting leaf color to: %s" % color) + pass + +func set_trunk_color(model_instance: Node3D): + #Log.pr("Setting trunk color to: %s" % color) + pass + +# Update a specific material by name with a new color +func update_material_by_name(root_node: Node, material_name: String, new_color: Color) -> bool: + var updated = false + + for mesh_instance in mesh_instances: + if update_material_in_mesh_instance(mesh_instance, material_name, new_color): + updated = true + + return updated + +# Update multiple materials at once +func update_materials_by_names(root_node: Node, material_updates: Dictionary) -> Dictionary: + var results = {} + + # Initialize results + for material_name in material_updates.keys(): + results[material_name] = false + + for mesh_instance in mesh_instances: + for material_name in material_updates.keys(): + var new_color = material_updates[material_name] + if update_material_in_mesh_instance(mesh_instance, material_name, new_color): + results[material_name] = true + + return results + +# Get all material names from a model +func get_all_material_names(root_node: Node) -> Array[String]: + var material_names: Array[String] = [] + + for mesh_instance in mesh_instances: + var mesh = mesh_instance.mesh + if mesh: + for i in range(mesh.get_surface_count()): + var material = mesh.surface_get_material(i) + if material and material.resource_name: + if not material.resource_name in material_names: + material_names.append(material.resource_name) + + return material_names + +func update_material_by_name_with_material(root_node: Node, material_name: String, new_material: StandardMaterial3D) -> bool: + var updated = false + + for mesh_instance in mesh_instances: + var mesh = mesh_instance.mesh + if mesh: + for i in range(mesh.get_surface_count()): + var current_material = mesh.surface_get_material(i) + if current_material and current_material.resource_name == material_name: + apply_material_with_queue(mesh_instance, i, new_material) + #mesh_instance.set_surface_override_material(i, new_material) + updated = true + #Log.pr("Updated material '%s' with pre-made material" % material_name) + + return updated + + +# Helper function - find all mesh instances recursively +func find_all_mesh_instances(node: Node) -> Array[MeshInstance3D]: + if node is MeshInstance3D: + mesh_instances.append(node) + + for child in node.get_children(): + mesh_instances += find_all_mesh_instances(child) + + return mesh_instances + +# Helper function - update material in a specific mesh instance +func update_material_in_mesh_instance(mesh_instance: MeshInstance3D, material_name: String, new_color: Color) -> bool: + var updated = false + var mesh = mesh_instance.mesh + + if mesh: + for i in range(mesh.get_surface_count()): + var material = mesh.surface_get_material(i) + if material and material.resource_name == material_name: + var new_material = material.duplicate() + new_material.albedo_color = new_color + mesh_instance.set_surface_override_material(i, new_material) + #Log.pr("Updated material '%s' to color: %s" % [material_name, new_color]) + updated = true + + return updated + + +func apply_material_with_queue(mesh_instance: MeshInstance3D, index: int, material: StandardMaterial3D): + ColorData.queue_material_application(mesh_instance, index, material) + return diff --git a/Entities/Tree/scripts/color_default.gd.uid b/Entities/Tree/scripts/color_default.gd.uid new file mode 100644 index 0000000..0b5a160 --- /dev/null +++ b/Entities/Tree/scripts/color_default.gd.uid @@ -0,0 +1 @@ +uid://dmgn1sve22e8a diff --git a/Entities/Tree/scripts/color_tree_birch.gd b/Entities/Tree/scripts/color_tree_birch.gd new file mode 100644 index 0000000..6bb6a4d --- /dev/null +++ b/Entities/Tree/scripts/color_tree_birch.gd @@ -0,0 +1,19 @@ +extends TreeColorDefault + +func set_leaf_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_leaf_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "leafsGreen", material) + + return + +func set_trunk_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_trunk_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "woodBark", material) + + return \ No newline at end of file diff --git a/Entities/Tree/scripts/color_tree_birch.gd.uid b/Entities/Tree/scripts/color_tree_birch.gd.uid new file mode 100644 index 0000000..03cf99f --- /dev/null +++ b/Entities/Tree/scripts/color_tree_birch.gd.uid @@ -0,0 +1 @@ +uid://dk04iuaocilih diff --git a/Entities/Tree/scripts/color_tree_elder.gd b/Entities/Tree/scripts/color_tree_elder.gd new file mode 100644 index 0000000..6bb6a4d --- /dev/null +++ b/Entities/Tree/scripts/color_tree_elder.gd @@ -0,0 +1,19 @@ +extends TreeColorDefault + +func set_leaf_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_leaf_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "leafsGreen", material) + + return + +func set_trunk_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_trunk_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "woodBark", material) + + return \ No newline at end of file diff --git a/Entities/Tree/scripts/color_tree_elder.gd.uid b/Entities/Tree/scripts/color_tree_elder.gd.uid new file mode 100644 index 0000000..3fef319 --- /dev/null +++ b/Entities/Tree/scripts/color_tree_elder.gd.uid @@ -0,0 +1 @@ +uid://3flh70jpmq2w diff --git a/Entities/Tree/scripts/color_tree_oak.gd b/Entities/Tree/scripts/color_tree_oak.gd new file mode 100644 index 0000000..2b14ada --- /dev/null +++ b/Entities/Tree/scripts/color_tree_oak.gd @@ -0,0 +1,19 @@ +extends TreeColorDefault + +func set_leaf_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_leaf_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "leafsGreen", material) + + return + +func set_trunk_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_trunk_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "woodBark", material) + + return diff --git a/Entities/Tree/scripts/color_tree_oak.gd.uid b/Entities/Tree/scripts/color_tree_oak.gd.uid new file mode 100644 index 0000000..614af49 --- /dev/null +++ b/Entities/Tree/scripts/color_tree_oak.gd.uid @@ -0,0 +1 @@ +uid://bjsj2j01nmnfq diff --git a/Entities/Tree/scripts/color_tree_pine.gd b/Entities/Tree/scripts/color_tree_pine.gd new file mode 100644 index 0000000..ed11292 --- /dev/null +++ b/Entities/Tree/scripts/color_tree_pine.gd @@ -0,0 +1,19 @@ +extends TreeColorDefault + +func set_leaf_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_leaf_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "leafsDark", material) + + return + +func set_trunk_color(model_instance: Node3D): + find_all_mesh_instances(model_instance) + + var material = ColorData.get_random_trunk_material(tree_name, current_season) + + update_material_by_name_with_material(model_instance, "woodBark", material) + + return diff --git a/Entities/Tree/scripts/color_tree_pine.gd.uid b/Entities/Tree/scripts/color_tree_pine.gd.uid new file mode 100644 index 0000000..bbc038b --- /dev/null +++ b/Entities/Tree/scripts/color_tree_pine.gd.uid @@ -0,0 +1 @@ +uid://de77441cemrqe diff --git a/Entities/Tree/scripts/tree.gd b/Entities/Tree/scripts/tree.gd index 586f44f..f780a49 100644 --- a/Entities/Tree/scripts/tree.gd +++ b/Entities/Tree/scripts/tree.gd @@ -20,10 +20,6 @@ func set_tree_data(data: TreeDataResource): tree_data = data spawn_model() -func setup_tree(data: TreeDataResource): - # Alternative method name if you prefer - set_tree_data(data) - func spawn_model(): if not tree_data or not tree_data.model: Log.pr("No tree data or model provided") @@ -37,7 +33,9 @@ func spawn_model(): # Instantiate the model from the TreeDataResource model_instance = tree_data.model.instantiate() + tree_data.apply_seasonal_colors(model_instance, "spring") add_child(model_instance) + # Create base circle after model is loaded if not base_circle: @@ -45,6 +43,7 @@ func spawn_model(): # Re-scan for mesh instances in the new model find_all_mesh_instances(model_instance) + update_outline_materials() # Apply any additional properties from tree_data @@ -63,7 +62,7 @@ func apply_tree_properties(): var scale_variation = randf_range(0.8, 1.2) model_instance.scale *= scale_variation -func setup_outline_material(): +func setup_outline_material() -> void: # Create outline material with your shader outline_material = ShaderMaterial.new() outline_material.shader = preload("res://outline.gdshader") diff --git a/Utilities/ColorStorage/ColorStorage.gd b/Utilities/ColorStorage/ColorStorage.gd new file mode 100644 index 0000000..3da66a7 --- /dev/null +++ b/Utilities/ColorStorage/ColorStorage.gd @@ -0,0 +1,119 @@ +class_name ColorStorageClass +extends Node + +var tree_collection = preload("res://Entities/Tree/Resources/TreeData.tres") as TreeDataCollection +var tree_materials: Dictionary = {} +var grass_materials: Dictionary = {"spring": {"base": Vector3(0.196, 0.392, 0.196), "top": Vector3(0.253, 0.492, 0.253), "bottom": Vector3(0.196, 0.392, 0.196)}, + "summer": {"base": null, "top": null, "bottom": null}, + "autumn": {"base": null, "top": null, "bottom": null}, + "winter": {"base": null, "top": null, "bottom": null}} + + +## TODO - Move this out into its own class +var material_application_queue: Array = [] +var max_materials_per_frame: int = 10 # Adjust based on performance + +@export var tree_colours: Array + +func _ready() -> void: + populate_tree_colors() + Log.pr("Tree colors populated: %d trees" % tree_materials.size()) + +func _process(_delta): + # Process material applications gradually over multiple frames + process_material_queue() + +func process_material_queue(): + var processed = 0 + while material_application_queue.size() > 0 and processed < max_materials_per_frame: + var task = material_application_queue.pop_front() + + # Check if the mesh instance is still valid before applying + if is_instance_valid(task.mesh_instance) and task.mesh_instance != null: + apply_material_immediately(task.mesh_instance, task.surface_index, task.material) + # If invalid, just skip this task (object was freed) + + processed += 1 + +func queue_material_application(mesh_instance: MeshInstance3D, surface_index: int, material: StandardMaterial3D): + material_application_queue.append({ + "mesh_instance": mesh_instance, + "surface_index": surface_index, + "material": material + }) + +func apply_material_immediately(mesh_instance: MeshInstance3D, surface_index: int, material: StandardMaterial3D): + if is_instance_valid(mesh_instance): + mesh_instance.set_surface_override_material(surface_index, material) + +# Create materials once at startup +func populate_tree_colors() -> void: + for tree in tree_collection.trees: + Log.pr("Loading tree: %s" % tree.tree_name) + + tree_materials[tree.tree_name] = { + "spring": { + "leaf": create_leaf_materials(tree.spring_leaf_color, "spring", tree.tree_name), + "trunk": create_trunk_materials(tree.spring_trunk_color, "spring", tree.tree_name) + }, + "summer": { + "leaf": create_leaf_materials(tree.summer_leaf_color, "summer", tree.tree_name), + "trunk": create_trunk_materials(tree.summer_trunk_color, "summer", tree.tree_name) + }, + "autumn": { + "leaf": create_leaf_materials(tree.autumn_leaf_color, "autumn", tree.tree_name), + "trunk": create_trunk_materials(tree.autumn_trunk_color, "autumn", tree.tree_name) + }, + "winter": { + "leaf": create_leaf_materials(tree.winter_leaf_color, "winter", tree.tree_name), + "trunk": create_trunk_materials(tree.winter_trunk_color, "winter", tree.tree_name) + } + } + +func create_leaf_materials(base_color: Color, season: String, tree_name: String) -> Dictionary: + var materials = {} + materials = { + "1": create_material(base_color, "%s_leaf_%s_primary" % [tree_name, season]), + "2": create_material(base_color.darkened(0.1), "%s_leaf_%s_secondary" % [tree_name, season]), + "3": create_material(base_color.lightened(0.1), "%s_leaf_%s_highlight" % [tree_name, season]), + "4": create_material(base_color.darkened(0.3), "%s_leaf_%s_shadow" % [tree_name, season]) + } + return materials + +func create_trunk_materials(base_color: Color, season: String, tree_name: String) -> Dictionary: + var materials = {} + materials = { + "1": create_material(base_color, "%s_trunk_%s_primary" % [tree_name, season]), + "2": create_material(base_color.darkened(0.2), "%s_trunk_%s_bark" % [tree_name, season]), + "3": create_material(Color(0.2, 0.4, 0.1), "%s_trunk_%s_moss" % [tree_name, season]), + "4": create_material(base_color.lightened(0.1), "%s_trunk_%s_highlight" % [tree_name, season]) + } + return materials + +func create_material(color: Color, material_name: String) -> StandardMaterial3D: + var material = StandardMaterial3D.new() + material.albedo_color = color + material.metallic = 0.0 + material.roughness = 0.8 + material.resource_name = material_name + return material + +func get_random_leaf_material(tree_name: String, season: String) -> StandardMaterial3D: + if tree_materials.has(tree_name) and tree_materials[tree_name].has(season): + var leaf_materials = tree_materials[tree_name][season]["leaf"] + var material_keys = leaf_materials.keys() + if material_keys.size() > 0: + var random_key = material_keys[randi() % material_keys.size()] + return leaf_materials[random_key] + + return create_material(Color.GREEN, "fallback_leaf") + +func get_random_trunk_material(tree_name: String, season: String) -> StandardMaterial3D: + if tree_materials.has(tree_name) and tree_materials[tree_name].has(season): + var trunk_materials = tree_materials[tree_name][season]["trunk"] + var material_keys = trunk_materials.keys() + if material_keys.size() > 0: + var random_key = material_keys[randi() % material_keys.size()] + return trunk_materials[random_key] + + return create_material(Color(0.6, 0.4, 0.2), "fallback_trunk") diff --git a/Utilities/ColorStorage/ColorStorage.gd.uid b/Utilities/ColorStorage/ColorStorage.gd.uid new file mode 100644 index 0000000..19f1cee --- /dev/null +++ b/Utilities/ColorStorage/ColorStorage.gd.uid @@ -0,0 +1 @@ +uid://bbtdrg8ax8sca diff --git a/falloff.gdshader b/falloff.gdshader new file mode 100644 index 0000000..ca3d193 --- /dev/null +++ b/falloff.gdshader @@ -0,0 +1,10 @@ +shader_type canvas_item; + +uniform sampler2D gradient_fallof; + +void light() { + float calculated_light_value = max(0.0, dot(NORMAL, LIGHT_DIRECTION)); + float sample = clamp(calculated_light_value * LIGHT_ENERGY, 0.05, 0.95); + vec4 shaded_texture = texture(gradient_fallof, vec2(sample, 0)); + LIGHT = vec4(LIGHT_COLOR.rgb * COLOR.rgb * shaded_texture.rgb, LIGHT_COLOR.a); +} \ No newline at end of file diff --git a/falloff.gdshader.uid b/falloff.gdshader.uid new file mode 100644 index 0000000..a9ae8bc --- /dev/null +++ b/falloff.gdshader.uid @@ -0,0 +1 @@ +uid://ce7q2m8ux2l0j diff --git a/project.godot b/project.godot index 3fd6561..b41fddb 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ DebugMenu="*res://addons/debug_menu/debug_menu.tscn" MapGeneration="*res://Utilities/MapGeneration/MapGeneration.gd" BiomeData="*res://Utilities/BiomeGeneration/BiomeData.gd" MapData="*res://Utilities/MapData/MapData.gd" +ColorData="*res://Utilities/ColorStorage/ColorStorage.gd" MapPopulation="*res://Utilities/MapData/MapPopulation.gd" [editor_plugins] diff --git a/stages/Test3D/GrassMaterialOverride.tres b/stages/Test3D/GrassMaterialOverride.tres index 09945b0..e564350 100644 --- a/stages/Test3D/GrassMaterialOverride.tres +++ b/stages/Test3D/GrassMaterialOverride.tres @@ -1,14 +1,17 @@ -[gd_resource type="ShaderMaterial" load_steps=3 format=3 uid="uid://b1miqvl8lus75"] +[gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://b1miqvl8lus75"] [ext_resource type="Shader" uid="uid://dbduq0qcaxmyi" path="res://grass.gdshader" id="1_vnnwo"] +[sub_resource type="FastNoiseLite" id="FastNoiseLite_vnnwo"] + [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_vnnwo"] +noise = SubResource("FastNoiseLite_vnnwo") [resource] render_priority = 0 shader = ExtResource("1_vnnwo") -shader_parameter/top_color = Color(0.102963, 0.465909, 0.382031, 1) -shader_parameter/bottom_color = Color(0.121494, 0.374954, 0.315972, 1) +shader_parameter/top_color = Color(0.252743, 0.492435, 0.25271, 1) +shader_parameter/bottom_color = Color(0.196078, 0.392157, 0.196078, 1) shader_parameter/ambient_occlusion_factor = 0.0 shader_parameter/specular_strength = 0.0 shader_parameter/player_displacement_strength = 1.0 diff --git a/stages/Test3D/Test3d.tscn b/stages/Test3D/Test3d.tscn index 662f6b0..518349c 100644 --- a/stages/Test3D/Test3d.tscn +++ b/stages/Test3D/Test3d.tscn @@ -740,7 +740,7 @@ script = ExtResource("2_sdmks") transform = Transform3D(1, 0, 0, 0, 1, -1.49012e-07, 0, 1.19209e-07, 1, 0.0410548, 0.237644, 3.45114) projection = 1 current = true -size = 4.0 +size = 3.0 near = 0.005 far = 100.0 @@ -772,7 +772,7 @@ draw_pass_1 = SubResource("QuadMesh_hvb1l") [node name="OmniLight3D" type="OmniLight3D" parent="SubViewportContainer/SubViewport/VFX/Fire"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000509977, 0.121094, -0.00151992) light_color = Color(0.89, 0.461613, 0.2136, 1) -light_energy = 0.668778 +light_energy = 0.802091 light_indirect_energy = 1.084 light_volumetric_fog_energy = 3.764 light_size = 0.105 diff --git a/stages/Test3D/assets/stylizedGrassMeshes/grass.res b/stages/Test3D/assets/stylizedGrassMeshes/grass.res index 25ccb3d0e371f88c573fb6ace4100d4fe1bbe913..598ca867466e4a751740cb9eb8ef22125d36b744 100644 GIT binary patch literal 3879 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%0APAE_85w;~eW42i`T#@eLc( zxW;%gzMIB2Vm#x!Wx&|(8OPXeyJ7s|y6JAQi|be{y~T7ZZkNtN?z!7nq*%ps%b_@K z6{k4vxKRvuboRGT9Ch2oZ?kQ)ZDO~y*Jis7ZMD}qHU}nN+So=unY6d@Go^jb+JEK^?bi4Go(^eLqqQ@iA(?h literal 3460 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