Skip to content

Commit

Permalink
Merge branch 'master' into godot4
Browse files Browse the repository at this point in the history
  • Loading branch information
Zylann authored Aug 28, 2023
2 parents e081c28 + 47ae27e commit 2018f3f
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 25 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ For a more detailed list of past and incoming changes, see the commit history.

- Added more explicit image format check before `update_collider` is called
- Used absolute path in shader #includes, so forking doesn't fail to find them by default
- Added 32-bit support for importing raw files
- Added 32-bit support for exporting raw files
- Added `HTerrainData.reload` function to allow reloading a terrain while the game is running (after saving in the editor)
- Update properties in the inspector when a custom shader is modified
- Added support for importing and exporting 32-bit raw files


1.7.2
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ stackdump (stackdump.eth)
Treer
MrGreaterThan
lenis0012
nan0m (Fabian)
```
34 changes: 22 additions & 12 deletions addons/zylann.hterrain/doc/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ For example, this code will tint the ground red at a specific position (in pixel
```gdscript
const HTerrainData = preload("res://addons/zylann.hterrain/hterrain_data.gd")
onready var _terrain = $Path/To/Terrain
@onready var _terrain = $Path/To/Terrain
func test():
# Get the image
Expand All @@ -903,9 +903,7 @@ func test():
# Modify the image
var position = Vector2(42, 36)
colormap.lock()
colormap.set_pixel(position, Color(1, 0, 0))
colormap.unlock()
# Notify the terrain of our change
data.notify_region_changed(Rect2(position.x, position.y, 1, 1), HTerrainData.CHANNEL_COLOR)
Expand Down Expand Up @@ -944,18 +942,14 @@ func _ready():
var terrain_data = HTerrainData.new()
terrain_data.resize(513)
var noise = OpenSimplexNoise.new()
var noise = FastNoiseLite.new()
var noise_multiplier = 50.0
# Get access to terrain maps
var heightmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_HEIGHT)
var normalmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_NORMAL)
var splatmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_SPLAT)
heightmap.lock()
normalmap.lock()
splatmap.lock()
# Generate terrain maps
# Note: this is an example with some arbitrary formulas,
# you may want to come up with your owns
Expand Down Expand Up @@ -984,10 +978,6 @@ func _ready():
normalmap.set_pixel(x, z, HTerrainData.encode_normal(normal))
splatmap.set_pixel(x, z, splat)
heightmap.unlock()
normalmap.unlock()
splatmap.unlock()
# Commit modifications so they get uploaded to the graphics card
var modified_region = Rect2(Vector2(), heightmap.get_size())
terrain_data.notify_region_change(modified_region, HTerrainData.CHANNEL_HEIGHT)
Expand Down Expand Up @@ -1017,6 +1007,26 @@ func _ready():
```


### Reload while the game is running

If your want to reload a terrain without restarting the game, you can do the following with a script:

```gdscript
# Reload terrain data from files, disregarding cached resources
terrain.data.reload()
# Update the collider, as it won't update automatically
terrain.update_collider()
```

So the following workflow is possible:

- While the game runs, do some changes to the terrain in the editor
- Save the scene containing the terrain
- Optional: tab in/out of Godot to make sure terrain textures get re-imported (if you don't do it, only the heightmap will update and shading might look wrong in changed areas)
- Press a hotkey in the game calling the code that reloads the terrain.


Export
----------

Expand Down
30 changes: 28 additions & 2 deletions addons/zylann.hterrain/hterrain.gd
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,39 @@ func _get_property_list():
]

if _material.shader != null:
var shader_params := RenderingServer.get_shader_parameter_list(_material.shader.get_rid())
var shader_params := _material.shader.get_shader_uniform_list(true)
for p in shader_params:
if _api_shader_params.has(p.name):
continue
var cp := {}
for k in p:
cp[k] = p[k]
cp.name = str("shader_params/", p.name)
# Godot has two ways of grouping properties in the inspector:
# - Prefixed properties using "/", which is part of the API property names
# - Group items in property lists, which are only a hint for the inspector display.
#
# In this plugin, just like ShaderMaterial, we need to nest shader parameters under
# a prefix to prevent conflicts with non-shader properties, which Godot interprets as
# a folder in the inspector.
#
# Godot 4.0 introduced `group_uniforms` in shaders, which also adds group items to
# shader property lists. When such groups are present, it creates repeating subgroups,
# which isn't desired.
# One way to workaround it is to set the `hint_string` of group items, to tell Godot to
# somewhat "ignore" the prefix when displaying them in the inspector, which will get
# rid of the unnecessary folders.
# We also have to prefix the parent group if any.
#
# Caveats: inspector will not display those uniforms under the `shader_params` folder.
# Not sure if we can get around that. ShaderMaterial has the same problem, and actually
# seems to do WAY more stuff to handle group_uniforms, so not sure if this simple code
# here is missing something.
# See https://github.com/Zylann/godot_heightmap_plugin/issues/394
if p.usage == PROPERTY_USAGE_GROUP:
cp.name = "Rendering/" + cp.name
cp.hint_string = "shader_params/"
else:
cp.name = str("shader_params/", p.name)
props.append(cp)

return props
Expand Down Expand Up @@ -961,6 +986,7 @@ func set_custom_shader(shader: Shader):

func _on_custom_shader_changed():
_material_params_need_update = true
notify_property_list_changed()


func _update_material_params():
Expand Down
43 changes: 36 additions & 7 deletions addons/zylann.hterrain/hterrain_data.gd
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,10 @@ func _deserialize_metadata(dict: Dictionary) -> bool:
return true


func load_data(dir_path: String):
func load_data(dir_path: String,
# Same as default in ResourceLoader.load()
resource_loader_cache_mode := ResourceLoader.CACHE_MODE_REUSE):

_locked = true

_load_metadata(dir_path.path_join(META_FILENAME))
Expand All @@ -1133,7 +1136,7 @@ func load_data(dir_path: String):

var channel_instance_sum = _get_total_map_count()
var pi = 0

# Note: if we loaded all maps at once before uploading them to VRAM,
# it would take a lot more RAM than if we load them one by one
for map_type in len(_maps):
Expand All @@ -1143,7 +1146,7 @@ func load_data(dir_path: String):
_logger.debug(str("Loading map ", get_map_debug_name(map_type, index),
" from ", _get_map_filename(map_type, index), "..."))

_load_map(dir_path, map_type, index)
_load_map(dir_path, map_type, index, resource_loader_cache_mode)

# A map that was just loaded is considered not modified yet
maps[index].modified = false
Expand All @@ -1159,6 +1162,26 @@ func load_data(dir_path: String):
resolution_changed.emit()


# Reloads the entire terrain from files, disregarding cached resources.
# This could be useful to reload a terrain while playing the game. You can do some edits in the
# editor, save the terrain and then reload in-game.
func reload():
_logger.debug("Reloading terrain data...")
var dir_path := resource_path.get_base_dir()
load_data(dir_path, ResourceLoader.CACHE_MODE_IGNORE)
_logger.debug("Reloading terrain data done")

# Debug
# var heightmap := get_image(CHANNEL_HEIGHT, 0)
# var im = Image.create(heightmap.get_width(), heightmap.get_height(), false, Image.FORMAT_RGB8)
# for y in heightmap.get_height():
# for x in heightmap.get_width():
# var h := heightmap.get_pixel(x, y).r * 0.1
# var g := h - floorf(h)
# im.set_pixel(x, y, Color(g, g, g, 1.0))
# im.save_png("local_tests/debug_data/reloaded_heightmap.png")


func get_data_dir() -> String:
# The HTerrainData resource represents the metadata and entry point for Godot.
# It should be placed within a folder dedicated for terrain storage.
Expand Down Expand Up @@ -1281,7 +1304,7 @@ static func _try_write_default_import_options(
HT_Util.write_import_file(defaults, imp_fpath, logger)


func _load_map(dir: String, map_type: int, index: int) -> bool:
func _load_map(dir: String, map_type: int, index: int, resource_loader_cache_mode : int) -> bool:
var fpath := dir.path_join(_get_map_filename(map_type, index))

# Maps must be configured before being loaded
Expand All @@ -1300,14 +1323,14 @@ func _load_map(dir: String, map_type: int, index: int) -> bool:
else:
fpath += ".res"

var tex = load(fpath)
var tex = ResourceLoader.load(fpath, "", resource_loader_cache_mode)

var must_load_image_in_editor := true

# Short-term compatibility with RGB8 encoding from the godot4 branch
if Engine.is_editor_hint() and tex == null and map_type == CHANNEL_HEIGHT:
var legacy_fpath := fpath.get_basename() + ".png"
var temp = load(legacy_fpath)
var temp = ResourceLoader.load(legacy_fpath, "", resource_loader_cache_mode)
if temp != null:
if temp is Texture2D:
temp = temp.get_image()
Expand All @@ -1327,7 +1350,8 @@ func _load_map(dir: String, map_type: int, index: int) -> bool:
map.image = tex
tex = ImageTexture.create_from_image(map.image)
must_load_image_in_editor = false


var texture_changed : bool = (tex != map.texture)
map.texture = tex

if Engine.is_editor_hint():
Expand All @@ -1343,6 +1367,11 @@ func _load_map(dir: String, map_type: int, index: int) -> bool:
if map_type == CHANNEL_HEIGHT:
_resolution = map.image.get_width()

# Initially added to support reloading of an existing terrain (otherwise it's pointless to emit
# during scene loading when the resource isn't assigned yet)
if texture_changed:
map_changed.emit(map_type, index)

return true


Expand Down
23 changes: 21 additions & 2 deletions addons/zylann.hterrain/hterrain_detail_layer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,17 @@ const _API_SHADER_PARAMS = {
@export var custom_shader : Shader:
get:
return custom_shader

set(shader):
if custom_shader == shader:
return

if custom_shader != null:
if Engine.is_editor_hint():
custom_shader.changed.disconnect(_on_custom_shader_changed)

custom_shader = shader

if custom_shader == null:
_material.shader = load(DEFAULT_SHADER_PATH)
else:
Expand All @@ -94,6 +101,10 @@ const _API_SHADER_PARAMS = {
# Ability to fork default shader
if shader.code == "":
shader.code = _default_shader.code

custom_shader.changed.connect(_on_custom_shader_changed)

notify_property_list_changed()


# Density modifier, to make more or less detail meshes appear overall.
Expand Down Expand Up @@ -238,14 +249,18 @@ func _get_property_list() -> Array:
# Dynamic properties coming from the shader
var props := []
if _material != null:
var shader_params = RenderingServer.get_shader_parameter_list(_material.shader.get_rid())
var shader_params = _material.shader.get_shader_uniform_list(true)
for p in shader_params:
if _API_SHADER_PARAMS.has(p.name):
continue
var cp := {}
for k in p:
cp[k] = p[k]
cp.name = str("shader_params/", p.name)
# See HTerrain._get_property_list for more information about this
if cp.usage == PROPERTY_USAGE_GROUP:
cp.hint_string = "shader_params/"
else:
cp.name = str("shader_params/", p.name)
props.append(cp)
return props

Expand Down Expand Up @@ -692,6 +707,10 @@ func get_cast_shadow() -> int:
return cast_shadow


func _on_custom_shader_changed():
notify_property_list_changed()


static func _generate_multimesh(resolution: int, density: float, mesh: Mesh, multimesh: MultiMesh):
assert(multimesh != null)

Expand Down

0 comments on commit 2018f3f

Please sign in to comment.