# grass.gd # This file is part of: SimpleGrassTextured # Copyright (c) 2023 IcterusGames # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @tool extends MultiMeshInstance3D ## Define your custom mesh per grass, you can open a .obj, .mesh, etc, or can ## copy paste your own mesh from any mesh component. Set as null for default ## SimpleGrassTextured mesh. @export var mesh : Mesh = null : set = _on_set_mesh ## Color albedo for mesh material @export_color_no_alpha var albedo := Color.WHITE : set = _on_set_albedo ## Texture albedo for mesh, you can apply normal, metallic and roughness ## textures on the "Material parameters" section @export var texture_albedo : Texture = load("res://addons/simplegrasstextured/textures/grassbushcc008.png") : set = _on_set_texture_albedo @export_group("Material parameters") ## Lets you setup a multi texture image by frames @export var texture_frames : Vector2i = Vector2i(1, 1) : set = _on_set_texture_frames; ## Defines the texture image alpha threshold @export_range(0.0, 1.0) var alpha_scissor_threshold := 0.5 : set = _on_set_alpha_scissor_threshold ## Ilumination mode[br] ## [b]Lambert[/b][br]Recomended for complex meshes such as flowers[br][br] ## [b]Normal grass[/b][br]The lighting will be calculated by the inclination of ## the grass, recommended for very simple meshes[br][br] ## [b]Unshaded[/b][br]No lighting will affect the grass @export_enum("Lambert", "Normal grass", "Unshaded") var light_mode := 1 : set = _on_set_light_mode @export_enum("Nearest", "Linear", "Nearest mipmap", "Linear mipmap") var texture_filter := 3 : set = _on_set_texture_filter @export_subgroup("Normal") @export var texture_normal : Texture = null : set = _on_set_texture_normal @export_range(-16.0, 16.0) var normal_scale := 1.0 : set = _on_set_normal_scale @export_subgroup("Metallic") @export var texture_metallic : Texture = null : set = _on_set_texture_metallic @export_enum("Red","Green","Blue","Alpha","Gray") var metallic_texture_channel : int = 0 : set = _on_set_metallic_texture_channel @export_range(0.0, 1.0) var metallic := 0.0 : set = _on_set_metallic @export_range(0.0, 1.0) var specular := 0.5 : set = _on_set_specular @export_subgroup("Roughness") @export var texture_roughness : Texture = null : set = _on_set_texture_roughness @export_enum("Red","Green","Blue","Alpha","Gray") var roughness_texture_channel : int = 0 : set = _on_set_roughness_texture_channel @export_range(0.0, 1.0) var roughness := 1.0 : set = _on_set_roughness @export_group("") ## Scale height factor of all the grass @export var scale_h := 1.0 : set = _on_set_scale_h ## Scale width factor of all the grass @export var scale_w := 1.0 : set = _on_set_scale_w ## Scale variable factor, scale of random grasses will be affected by this ## factor @export var scale_var := -0.25 : set = _on_set_scale_var ## Defines the strength of this grass, with large values ​​the grass will not be ## moved by the wind, for example a bamboo can be 0.9 (it will almost not be ## affected by the wind), and a tall grass can be 0.2 (very affected by the ## wind) @export_range(0.0, 1.0) var grass_strength := 0.55 : set = _on_set_grass_strength ## If true, this grass will be in "interactive mode", that means if an object ## is near the grass, the grass will react and move.[br][br] ## [b]To setup the "interactive mode":[/b][br] ## 1. You must enable by code on the begin of your scene by call ## [code]SimpleGrass.set_interactive(true)[/code][br] ## 2. Setup your objects to be visible on the Visual Layer 17[br] ## 3. Update the SimpleGrassTexture camera position by calling ## [code]SimpleGrass.set_player_position()[/code] regulary (on your _process ## function by example)[br][br] ## [b]You can see how to enable "interactive mode" on:[/b][br] ## [url]https://github.com/IcterusGames/SimpleGrassTextured?tab=readme-ov-file#how-to-enable-interactive-mode[/url] @export var interactive : bool = true : set = _on_set_interactive @export_group("Advanced") ## Allows you to define how much the grass will react to objects on axis X and Z @export var interactive_level_xz : float = 3.0 : set = _on_set_interactive_level_xz ## Allows you to define how much the grass will react to objects on axis Y @export var interactive_level_y : float = 0.3 : set = _on_set_interactive_level_y ## Locks the scale node of SimpleGrassTextured to 1 @export var disable_node_scale := true : set = _on_set_disable_node_scale ## Disable the ability to rotate the SimpleGrassTextured node @export var disable_node_rotation := true : set = _on_set_disable_node_rotation @export_group("Optimization") @export var optimization_by_distance := false : set = _on_set_optimization_by_distance @export var optimization_level := 7.0 : set = _on_set_optimization_level @export var optimization_dist_min := 10.0 : set = _on_set_optimization_dist_min @export var optimization_dist_max := 50.0 : set = _on_set_optimization_dist_max @export_group("Height Map Data") ## This is the baked height map of this grass, this will speed up the load of ## the scene. To setup this variable use the menu ## SimpleGrassTextured->"Bake height map" on the Editor 3D @export var baked_height_map : Image = null @export_group("Draw Collision Mask") ## This is the collision mask for drawing, this allows you to define what your ## terrain collision mask is, that way it will be easier to draw your grass. @export_flags_3d_physics var collision_mask :int = pow(2, 32) - 1 var sgt_radius := 2.0 var sgt_density := 25 var sgt_scale := 1.0 var sgt_rotation := 0.0 var sgt_rotation_rand := 1.0 var sgt_dist_min := 0.25 var sgt_follow_normal := false var sgt_slope := Vector2(0, 45) var sgt_tool_shape := {} var temp_dist_min := 0.0 # Deprecated vars: var player_pos := Vector3(1000000, 1000000, 1000000) : set = _on_set_player_pos var player_radius := 0.5 : set = _on_set_player_radius var wind_dir := Vector3.RIGHT : set = _on_set_wind_dir var wind_strength := 0.15 : set = _on_set_wind_strength var wind_turbulence := 1.0 : set = _on_set_wind_turbulence var wind_pattern : Texture = null : set = _on_set_wind_pattern var _default_mesh : Mesh = load("res://addons/simplegrasstextured/default_mesh.tres").duplicate() var _buffer_add : Array[Transform3D] = [] var _material := load("res://addons/simplegrasstextured/materials/grass.material").duplicate() as ShaderMaterial var _force_update_multimesh := false var _properties = [] var _node_height_map = null var _singleton = null var _wrng_deprec_playerpos = true var _wrng_deprec_playerrad = true var _wrng_deprec_windir = true var _wrng_deprec_windstrng = true var _wrng_deprec_windturb = true var _wrng_deprec_windpatt = true func _init(): if Engine.is_editor_hint(): if collision_mask == pow(2, 32) - 1: collision_mask = ProjectSettings.get_setting("SimpleGrassTextured/General/default_terrain_physics_layer", pow(2, 32) -1) for var_i in get_property_list(): if not var_i.name.begins_with("sgt_"): continue _properties.append({ "name": var_i.name, "type": var_i.type, "usage": PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE, }) func _ready(): if Engine.is_editor_hint(): set_process(true) else: set_process(false) _singleton = get_node("/root/SimpleGrass") if not has_meta(&"SimpleGrassTextured"): set_meta(&"SimpleGrassTextured", "2.0.5") else: if get_meta(&"SimpleGrassTextured") == "1.0.2": # New default mesh update tangents set_meta(&"SimpleGrassTextured", "2.0.3") _force_update_multimesh = true if multimesh != null: if mesh != null: multimesh.mesh = mesh else: multimesh.mesh = _default_mesh if get_meta(&"SimpleGrassTextured") == "2.0.3": set_meta(&"SimpleGrassTextured", "2.0.5") disable_node_scale = false disable_node_rotation = false if multimesh == null: multimesh = MultiMesh.new() multimesh.transform_format = MultiMesh.TRANSFORM_3D if multimesh.mesh == null: if mesh != null: multimesh.mesh = mesh else: multimesh.mesh = _default_mesh _update_material_shader() for isur in range(multimesh.mesh.get_surface_count()): if multimesh.mesh.surface_get_material(isur) != null: _material = multimesh.mesh.surface_get_material(isur) if _material.get_reference_count() > 2: _material = _material.duplicate() break for isur in range(multimesh.mesh.get_surface_count()): if multimesh.mesh.surface_get_material(isur) == null: multimesh.mesh.surface_set_material(isur, _material) set_disable_scale(disable_node_scale) if disable_node_rotation: set_notify_transform(true) update_all_material() func _notification(what: int) -> void: if what == NOTIFICATION_TRANSFORM_CHANGED: if disable_node_rotation and global_rotation != Vector3.ZERO: var prev_scale := scale global_rotation = Vector3.ZERO scale = prev_scale func update_all_material(): _on_set_albedo(albedo) _on_set_texture_albedo(texture_albedo) _on_set_alpha_scissor_threshold(alpha_scissor_threshold) _on_set_light_mode(light_mode) _on_set_texture_normal(texture_normal) _on_set_normal_scale(normal_scale) _on_set_texture_metallic(texture_metallic) _on_set_metallic_texture_channel(metallic_texture_channel) _on_set_metallic(metallic) _on_set_specular(specular) _on_set_texture_roughness(texture_roughness) _on_set_roughness_texture_channel(roughness_texture_channel) _on_set_roughness(roughness) _on_set_scale_h(scale_h) _on_set_scale_w(scale_w) _on_set_scale_var(scale_var) _on_set_grass_strength(grass_strength) _on_set_interactive(interactive) _on_set_interactive_level_xz(interactive_level_xz) _on_set_interactive_level_y(interactive_level_y) _on_set_optimization_by_distance(optimization_by_distance) _on_set_optimization_level(optimization_level) _on_set_optimization_dist_min(optimization_dist_min) _on_set_optimization_dist_max(optimization_dist_max) func _enter_tree(): if interactive: _update_height_map.call_deferred() func _exit_tree(): if _node_height_map: _node_height_map.queue_free() _node_height_map = null func _process(_delta : float): if _buffer_add.size() != 0 or _force_update_multimesh: _force_update_multimesh = false _update_multimesh() func _get_property_list() -> Array: if _properties == null: return [] return _properties func eval_grass_transform(pos : Vector3, normal : Vector3, scale : Vector3, rotated : float) -> Transform3D: var trans := Transform3D() if abs(normal.z) == 1: trans.basis.x = Vector3(1,0,0) trans.basis.y = Vector3(0,0,normal.z) trans.basis.z = Vector3(0,normal.z,0) trans.basis = trans.basis.orthonormalized() else: trans.basis.y = normal trans.basis.x = normal.cross(trans.basis.z) trans.basis.z = trans.basis.x.cross(normal) trans.basis = trans.basis.orthonormalized() trans = trans.rotated_local(Vector3.UP, rotated) trans = trans.scaled(scale) trans = trans.translated(pos) return trans func add_grass(pos : Vector3, normal : Vector3, scale : Vector3, rotated : float): var trans := eval_grass_transform(pos, normal, scale, rotated) if sgt_dist_min > 0: for trans_prev in _buffer_add: if trans.origin.distance_to(trans_prev.origin) <= sgt_dist_min: return _buffer_add.append(trans) func add_grass_batch(transforms : Array): var distmin = temp_dist_min if temp_dist_min == 0: distmin = sgt_dist_min if _buffer_add.size() > 0 and distmin > 0: for trans_prev in _buffer_add: for trans in transforms: if trans.origin.distance_to(trans_prev.origin) <= distmin: transforms.erase(trans) break _buffer_add.append_array(transforms) func erase(pos: Vector3, radius: float) -> void: if not multimesh.get_aabb().intersects(AABB(pos - Vector3(radius, radius, radius), Vector3(radius, radius, radius) * 2)): return _apply_erase_tool(func(array: Array[Transform3D]) -> int: var num_to_erase := 0 for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) if trans.origin.distance_to(pos) > radius: array.append(trans) else: num_to_erase += 1 return num_to_erase ) func erase_cylinder(pos: Vector3, rx: float, height: float, rz: float, shape_transform: Transform3D) -> void: var aabb := AABB(Vector3(-rx, -height / 2, -rz), Vector3(rx, height / 2, rz) * 2) aabb = shape_transform * aabb if not (global_transform * multimesh.get_aabb()).intersects(aabb): return _apply_erase_tool(func(array: Array[Transform3D]) -> int: var num_to_erase := 0 for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) var point := global_transform * trans.origin * shape_transform var r = (point.x * point.x) / (rx * rx) + (point.z * point.z) / (rz * rz) if point.y < -height or point.y > height or r >= 1: array.append(trans) else: num_to_erase += 1 return num_to_erase ) func erase_box(pos: Vector3, size: Vector3, shape_transform: Transform3D) -> void: var aabb := AABB(-size / 2, size) if not (global_transform * multimesh.get_aabb()).intersects(shape_transform * aabb): return _apply_erase_tool(func(array: Array[Transform3D]) -> int: var num_to_erase := 0 for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) if not aabb.has_point(global_transform * trans.origin * shape_transform): array.append(trans) else: num_to_erase += 1 return num_to_erase ) func snap_to_terrain() -> void: await get_tree().physics_frame for i in multimesh.instance_count: var trans := multimesh.get_instance_transform(i) var origin_ws := to_global(trans.origin) var rc = _raycast(self, origin_ws + Vector3.UP * 100, Vector3.DOWN, 1000, collision_mask) if not rc: continue trans.origin = to_local(rc["position"]) var normal : Vector3 = rc["normal"] if sgt_follow_normal else Vector3.UP if abs(normal.z) == 1: trans.basis.x = Vector3(1, 0, 0) trans.basis.y = Vector3(0, 0, normal.z) trans.basis.z = Vector3(0, normal.z, 0) trans.basis = trans.basis.orthonormalized() else: trans.basis.y = normal trans.basis.x = normal.cross(trans.basis.z) trans.basis.z = trans.basis.x.cross(normal) multimesh.set_instance_transform(i, trans) recalculate_custom_aabb() static func _raycast(from: Node3D, pos: Vector3, dir: Vector3, dist: float, mask: int) -> Dictionary: var space_state = from.get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(pos, pos + dir * dist, mask, [from]) return space_state.intersect_ray(query) func _apply_erase_tool(func_tool: Callable): var multi_new := MultiMesh.new() var array : Array[Transform3D] = [] multi_new.transform_format = MultiMesh.TRANSFORM_3D if mesh != null: multi_new.mesh = mesh else: multi_new.mesh = _default_mesh if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh if func_tool.call(array) == 0: return multi_new.instance_count = array.size() for i in range(array.size()): multi_new.set_instance_transform(i, array[i]) if Engine.is_editor_hint() and multimesh.resource_path.length(): var path := multimesh.resource_path multimesh = multi_new multimesh.take_over_path(path) else: multimesh = multi_new if _material != null: for isur in range(multimesh.mesh.get_surface_count()): if multimesh.mesh.surface_get_material(isur) == null: multimesh.mesh.surface_set_material(isur, _material) if Engine.is_editor_hint(): baked_height_map = null custom_aabb.position = Vector3.ZERO custom_aabb.end = Vector3.ZERO func auto_center_position(): if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh var aabb : AABB = multimesh.get_aabb() var center : Vector3 = global_position + aabb.position + (aabb.size / 2) var align : Vector3 = global_position - center if center == global_position: return global_position = center var multi_new := MultiMesh.new() multi_new.transform_format = MultiMesh.TRANSFORM_3D if mesh != null: multi_new.mesh = mesh else: multi_new.mesh = _default_mesh multi_new.instance_count = multimesh.instance_count for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) trans.origin += align multi_new.set_instance_transform(i, trans) if Engine.is_editor_hint() and multimesh.resource_path.length(): var path := multimesh.resource_path multimesh = multi_new multimesh.take_over_path(path) else: multimesh = multi_new if _material != null: for isur in range(multimesh.mesh.get_surface_count()): if multimesh.mesh.surface_get_material(isur) == null: multimesh.mesh.surface_set_material(isur, _material) if Engine.is_editor_hint(): if baked_height_map != null: baked_height_map = null bake_height_map() custom_aabb.position = Vector3.ZERO custom_aabb.end = Vector3.ZERO else: baked_height_map = null func recalculate_custom_aabb(): if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh var start := Vector3.ONE * 0x7FFFFFFF var end := start * -1 var mesh_end := multimesh.mesh.get_aabb().end * Vector3(scale_w, scale_h, scale_w) for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) var point : Vector3 = trans * mesh_end if point.x < start.x: start.x = point.x if point.y < start.y: start.y = point.y if point.z < start.z: start.z = point.z point = trans * mesh_end if point.x > end.x: end.x = point.x if point.y > end.y: end.y = point.y if point.z > end.z: end.z = point.z custom_aabb.position = start custom_aabb.end = end func _update_multimesh(): if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh var multi_new := MultiMesh.new() var count_prev := multimesh.instance_count multi_new.transform_format = MultiMesh.TRANSFORM_3D if mesh != null: multi_new.mesh = mesh else: multi_new.mesh = _default_mesh if count_prev > 0 and _buffer_add.size() > 0 and (sgt_dist_min > 0 or temp_dist_min > 0): var pos_min := Vector3(10000000, 10000000, 10000000) var pos_max := pos_min * -1 for trans in _buffer_add: if pos_min.x > trans.origin.x: pos_min.x = trans.origin.x if pos_min.y > trans.origin.y: pos_min.y = trans.origin.y if pos_min.z > trans.origin.z: pos_min.z = trans.origin.z if pos_max.x < trans.origin.x: pos_max.x = trans.origin.x if pos_max.y < trans.origin.y: pos_max.y = trans.origin.y if pos_max.z < trans.origin.z: pos_max.z = trans.origin.z pos_min -= Vector3.ONE pos_max += Vector3.ONE var dist_min := temp_dist_min if dist_min == 0: dist_min = sgt_dist_min for i in range(multimesh.instance_count): var trans := multimesh.get_instance_transform(i) if trans.origin.x < pos_min.x or trans.origin.x > pos_max.x: continue if trans.origin.y < pos_min.y or trans.origin.y > pos_max.y: continue if trans.origin.z < pos_min.z or trans.origin.z > pos_max.z: continue for trans_add in _buffer_add: if trans_add.origin.distance_to(trans.origin) > dist_min: continue _buffer_add.erase(trans_add) break if _buffer_add.size() == 0: return multi_new.instance_count = count_prev + _buffer_add.size() for i in range(multimesh.instance_count): multi_new.set_instance_transform(i, multimesh.get_instance_transform(i)) for i in range(_buffer_add.size()): multi_new.set_instance_transform(i + count_prev, _buffer_add[i]) if Engine.is_editor_hint() and multimesh.resource_path.length(): var path := multimesh.resource_path multimesh = multi_new multimesh.take_over_path(path) else: multimesh = multi_new if _material != null: for isur in range(multimesh.mesh.get_surface_count()): if multimesh.mesh.surface_get_material(isur) == null: multimesh.mesh.surface_set_material(isur, _material) _buffer_add.clear() temp_dist_min = 0 if Engine.is_editor_hint(): baked_height_map = null custom_aabb.position = Vector3.ZERO custom_aabb.end = Vector3.ZERO func _create_height_map_image(local : bool) -> Image: if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh var aabb : AABB = multimesh.get_aabb() var img_size := Vector2i( clamp(snappedi(aabb.size.x * 4, 32), 32, 128), clamp(snappedi(aabb.size.z * 4, 32), 32, 128) ) var img := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8) var relx := float(img_size.x) / aabb.size.x var relz := float(img_size.y) / aabb.size.z img.fill(Color(1.0, 1.0, 1.0, 0.0)) for i in range(multimesh.instance_count): var trans : Transform3D = multimesh.get_instance_transform(i) trans.origin -= aabb.position var x := clampi(int(trans.origin.x * relx), 0, img_size.x) var y := clampi(int(trans.origin.z * relz), 0, img_size.y) var posy := trans.origin.y + aabb.position.y + 16200.0 if local: posy += global_position.y var r := (floorf(posy / 180.0) + 75.0) / 255.0 var g := (floorf(posy - ((roundf(r * 255.0) - 75.0) * 180.0)) + 75.0) / 255.0 var b := fmod(absf(posy), 1.0) var color := Color(r, g, b, 1.0) img.set_pixel(x, y, color) for n in range(1, 3): if x - n >= 0 and img.get_pixel(x - n, y).a == 0: img.set_pixel(x - n, y, color) if y - n >= 0 and img.get_pixel(x - n, y - n).a == 0: img.set_pixel(x - n, y - n, color) if y + n < img_size.y and img.get_pixel(x - n, y + n).a == 0: img.set_pixel(x - n, y + n, color) if x + n < img_size.x and img.get_pixel(x + n, y).a == 0: img.set_pixel(x + n, y, color) if y - n >= 0 and img.get_pixel(x + n, y - n).a == 0: img.set_pixel(x + n, y - n, color) if y + n < img_size.y and img.get_pixel(x + n, y + n).a == 0: img.set_pixel(x + n, y + n, color) if y - n >= 0 and img.get_pixel(x, y - n).a == 0: img.set_pixel(x, y - n, color) if y + n < img_size.y and img.get_pixel(x, y + n).a == 0: img.set_pixel(x, y + n, color) return img func _local_height_map_to_global(img : Image) -> Image: var result : Image = Image.create(img.get_width(), img.get_height(), false, Image.FORMAT_RGBA8) result.fill(Color(0, 0, 0, 0)) for y in img.get_height(): for x in img.get_width(): var color : Color = img.get_pixel(x, y) if color.a == 0: continue var posy : float = (((roundf(color.r * 255.0) - 75.0) * 180.0) + (roundf(color.g * 255.0) - 75.0) + color.b) posy += global_position.y color.r = (floorf(posy / 180.0) + 75.0) / 255.0 color.g = (floorf(posy - ((roundf(color.r * 255.0) - 75.0) * 180.0)) + 75.0) / 255.0 color.b = fmod(absf(posy), 1.0) result.set_pixel(x, y, color) return result func bake_height_map(): if not Engine.is_editor_hint(): return null if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh await get_tree().process_frame var _dummy = multimesh.buffer.size() await get_tree().process_frame var img : Image = _create_height_map_image(false) baked_height_map = img func clear_all(): if multimesh == null: multimesh = MultiMesh.new() if Engine.is_editor_hint() and multimesh.resource_path.length(): var path := multimesh.resource_path multimesh = MultiMesh.new() multimesh.take_over_path(path) else: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh multimesh.transform_format = MultiMesh.TRANSFORM_3D if Engine.is_editor_hint(): if baked_height_map != null: baked_height_map = null bake_height_map() custom_aabb.position = Vector3.ZERO custom_aabb.end = Vector3.ZERO func _update_height_map(): if Engine.is_editor_hint(): return if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh var img : Image = null if baked_height_map == null: await get_tree().process_frame var _dummy = multimesh.buffer.size() await get_tree().process_frame img = _create_height_map_image(true) else: img = _local_height_map_to_global(baked_height_map) var aabb : AABB = multimesh.get_aabb() var texture := ImageTexture.create_from_image(img) if _node_height_map != null: _node_height_map.queue_free() _node_height_map = MeshInstance3D.new() _node_height_map.mesh = PlaneMesh.new() _node_height_map.mesh.size = Vector2(aabb.size.x, aabb.size.z) var mat = load("res://addons/simplegrasstextured/materials/position.material").duplicate(true) mat.set_shader_parameter("texture_albedo", texture) _node_height_map.material_override = mat _singleton._height_view.add_child(_node_height_map) var align := Vector3( (aabb.position.x + (aabb.size.x / 2.0)), 0, (aabb.position.z + (aabb.size.z / 2.0)) ) _node_height_map.global_position = global_position + align _node_height_map.visible = visible func _update_material_shader() -> bool: var shader_name := "grass" if light_mode == 2: shader_name += "_unshaded" # "Nearest" = 0, "Linear" = 1, "Nearest mipmap" = 2, "Linear mipmap" = 3 if texture_filter == 0: shader_name += "_nearest" elif texture_filter == 1: shader_name += "_linear" elif texture_filter == 2: shader_name += "_nearest_mipmap" elif texture_filter == 3: shader_name += "" # Linear mipmap is the default filter if _material.get_shader().resource_path != "res://addons/simplegrasstextured/shaders/" + shader_name + ".gdshader": _material.shader = load("res://addons/simplegrasstextured/shaders/" + shader_name + ".gdshader") if _material.get_shader() == null: _material.shader = load("res://addons/simplegrasstextured/shaders/grass.gdshader") _material.shader.take_over_path("res://addons/simplegrasstextured/shaders/" + shader_name + ".gdshader") return true return false func _on_set_mesh(value : Mesh): mesh = value if _material != null: if mesh != null: _material.set_shader_parameter("grass_size_y", mesh.get_aabb().size.y) else: _material.set_shader_parameter("grass_size_y", 1.0) if Engine.is_editor_hint() and is_inside_tree(): _update_multimesh() func _on_set_albedo(value : Color): albedo = value; if _material != null: _material.set_shader_parameter("albedo", albedo) func _on_set_texture_albedo(value : Texture): texture_albedo = value if _material != null: _material.set_shader_parameter("texture_albedo", texture_albedo) func _on_set_texture_frames(value : Vector2i): texture_frames = value if texture_frames.x <= 0: texture_frames.x = 1 if texture_frames.y <= 0: texture_frames.y = 1 if _material != null: _material.set_shader_parameter("texture_frames", Vector2(texture_frames.x, texture_frames.y)) func _on_set_light_mode(value : int): light_mode = value if _material == null: return if _update_material_shader(): if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh if multimesh.mesh != null: for isur in range(multimesh.mesh.get_surface_count()): multimesh.mesh.surface_set_material(isur, _material) update_all_material() _material.set_shader_parameter("light_mode", light_mode) func _on_set_texture_filter(value : int) -> void: texture_filter = value if _material == null: return if _update_material_shader(): if multimesh == null: multimesh = MultiMesh.new() multimesh.mesh = mesh if mesh != null else _default_mesh if multimesh.mesh != null: for isur in range(multimesh.mesh.get_surface_count()): multimesh.mesh.surface_set_material(isur, _material) update_all_material() func _on_set_texture_normal(value : Texture): texture_normal = value if _material != null: _material.set_shader_parameter("texture_normal", texture_normal) func _on_set_normal_scale(value : float): normal_scale = value if _material != null: _material.set_shader_parameter("normal_scale", normal_scale) func _on_set_texture_metallic(value : Texture): texture_metallic = value if _material != null: _material.set_shader_parameter("texture_metallic", texture_metallic) func _on_set_metallic_texture_channel(value : int): metallic_texture_channel = value if _material != null: var channel : Vector4 if value == 0: channel = Vector4(1,0,0,0) elif value == 1: channel = Vector4(0,1,0,0) elif value == 2: channel = Vector4(0,0,1,0) elif value == 3: channel = Vector4(0,0,0,1) elif value == 4: channel = Vector4(1,1,1,1) _material.set_shader_parameter("metallic_texture_channel", channel) func _on_set_metallic(value : float): metallic = value if _material != null: _material.set_shader_parameter("metallic", metallic) func _on_set_specular(value : float): specular = value if _material != null: _material.set_shader_parameter("specular", specular) func _on_set_texture_roughness(value : Texture): texture_roughness = value if _material != null: _material.set_shader_parameter("texture_roughness", texture_roughness) func _on_set_roughness_texture_channel(value : int): roughness_texture_channel = value if _material != null: var channel : Vector4 if value == 0: channel = Vector4(1,0,0,0) elif value == 1: channel = Vector4(0,1,0,0) elif value == 2: channel = Vector4(0,0,1,0) elif value == 3: channel = Vector4(0,0,0,1) elif value == 4: channel = Vector4(1,1,1,1) _material.set_shader_parameter("roughness_texture_channel", channel) func _on_set_roughness(value : float): roughness = value if _material != null: _material.set_shader_parameter("roughness", roughness) func _on_set_alpha_scissor_threshold(value : float): alpha_scissor_threshold = value if _material != null: _material.set_shader_parameter("alpha_scissor_threshold", alpha_scissor_threshold) func _on_set_scale_h(value : float): scale_h = value if _material != null: _material.set_shader_parameter("scale_h", scale_h) func _on_set_scale_w(value : float): scale_w = value if _material != null: _material.set_shader_parameter("scale_w", scale_w) func _on_set_scale_var(value : float): scale_var = value if _material != null: _material.set_shader_parameter("scale_var", scale_var) func _on_set_grass_strength(value : float): grass_strength = value if _material != null: _material.set_shader_parameter("grass_strength", grass_strength) func _on_set_interactive(value : bool): if interactive == value: return interactive = value if Engine.is_editor_hint(): return if interactive: _update_height_map() else: if _node_height_map != null: _node_height_map.queue_free() _node_height_map = null if _material != null: _material.set_shader_parameter("interactive_mode", interactive) func _on_set_interactive_level_xz(value : float): interactive_level_xz = value if _material != null: _material.set_shader_parameter("interactive_level_xz", interactive_level_xz) func _on_set_interactive_level_y(value : float): interactive_level_y = value if _material != null: _material.set_shader_parameter("interactive_level_y", interactive_level_y) func _on_set_disable_node_scale(value : bool) -> void: disable_node_scale = value if disable_node_scale: scale = Vector3.ONE set_disable_scale(disable_node_scale) func _on_set_disable_node_rotation(value : bool) -> void: disable_node_rotation = value if disable_node_rotation: var prev_scale := scale global_rotation = Vector3.ZERO scale = prev_scale set_notify_transform(true) func _on_set_optimization_by_distance(value : bool): optimization_by_distance = value if _material != null: _material.set_shader_parameter("optimization_by_distance", optimization_by_distance) func _on_set_optimization_level(value : float): optimization_level = value if _material != null: _material.set_shader_parameter("optimization_level", optimization_level) func _on_set_optimization_dist_min(value : float): optimization_dist_min = value if _material != null: _material.set_shader_parameter("optimization_dist_min", optimization_dist_min) func _on_set_optimization_dist_max(value : float): optimization_dist_max = value if _material != null: _material.set_shader_parameter("optimization_dist_max", optimization_dist_max) func _on_set_player_pos(value : Vector3): player_pos = Vector3(1000000, 1000000, 1000000) if value != Vector3(1000000, 1000000, 1000000): #_singleton.set_player_position(value) if _wrng_deprec_playerpos and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_playerpos = false push_warning("Simple Grass Textured: ("+name+") player_pos parameter is deprecated, use SimpleGrass.set_player_position") func _on_set_player_radius(value : float): player_radius = 0.5 if _wrng_deprec_playerrad and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_playerrad = false push_warning("Simple Grass Textured: ("+name+") player_radius parameter is deprecated") func _on_set_wind_dir(value : Vector3): wind_dir = Vector3.RIGHT #_singleton.wind_direction = value if _wrng_deprec_windir and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_windir = false push_warning("Simple Grass Textured: ("+name+") wind_dir parameter is deprecated, use SimpleGrass.wind_direction") func _on_set_wind_strength(value : float): wind_strength = 0.15 #_singleton.wind_strength = value if _wrng_deprec_windstrng and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_windstrng = false push_warning("Simple Grass Textured: ("+name+") wind_strength parameter is deprecated, use SimpleGrass.wind_strength") func _on_set_wind_turbulence(value : float): wind_turbulence = 1.0 #_singleton.wind_turbulence = value if _wrng_deprec_windturb and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_windturb = false push_warning("Simple Grass Textured: ("+name+") wind_turbulence parameter is deprecated, use SimpleGrass.wind_turbulence") func _on_set_wind_pattern(value : Texture): wind_pattern = null #RenderingServer.global_shader_parameter_set("sgt_wind_pattern", value) if value != null and _wrng_deprec_windpatt and (Engine.is_editor_hint() or OS.is_debug_build()): _wrng_deprec_windpatt = false push_warning("Simple Grass Textured: ("+name+") wind_pattern parameter is deprecated, use SimpleGrass.set_wind_pattern")