Grassy knoll

This commit is contained in:
2026-03-19 11:35:38 -05:00
parent 42e49b5cd8
commit b5c7c7c8b6
124 changed files with 7319 additions and 119 deletions
+973
View File
@@ -0,0 +1,973 @@
# 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")