godot-extended-libraries/godot-lod

A small suggestion to script (refactor and usability improvement)

roalyr opened this issue · 3 comments

I'll leave it here just in case. don't want to poke you over this since Godot 4 has it covered, but still.
What's changed:

  1. Structured scene.
  2. Meshes are attached as children to Scene_items nodes, so no need to name them in any specific way.
  3. LOD distances are relative, values are multiplied by the size of the parent node LODs
  4. removed get_children() loop.

Scene with embedded script:
image

[gd_scene load_steps=2 format=2]

[sub_resource type="GDScript" id=1]
script/source = "# Copyright © 2020 Hugo Locurcio and contributors - MIT License
# See `LICENSE.md` included in the source distribution for details.
extends Spatial

# If `false`, LOD won't update anymore. This can be used for performance comparison
# purposes.
export var enable_lod = true

# The maximum LOD 0 (high quality) distance in units.
export var lod_0_relative_distance = 5

# The maximum LOD 1 (medium quality) distance in units.
export var lod_1_relative_distance = 25

# The maximum LOD 2 (low quality) distance in units.
# Past this distance, all LOD variants are hidden.
export var lod_2_relative_distance = 150

# The rate at which LODs will be updated (in seconds). Lower values are more reactive
# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes.
# Set this accordingly depending on your camera movement speed.
# The default value should suit most projects already.
# Note: Slow cameras don't need to have LOD-enabled objects update their status often.
# This can overridden by setting the project setting `lod/refresh_rate`.
export var refresh_rate = 0.25

# The internal refresh timer.
var timer = 0.0


func _ready():

	# Add random jitter to the timer to ensure LODs don't all swap at the same time.
	randomize()
	timer += rand_range(0, refresh_rate)


# Despite LOD not being related to physics, we chose to run in `_physics_process()`
# to minimize the amount of method calls per second (and therefore decrease CPU usage).
func _physics_process(delta):
	if not enable_lod:
		# Show
		show_scenes(\"LOD0\")
		# Hide
		hide_scenes(\"LOD1\")
		hide_scenes(\"LOD2\")
		hide_scenes(\"LOD3\")
		return

	# We need a camera to do the rest.
	var camera = get_viewport().get_camera()
	if camera == null:
		return

	if timer <= refresh_rate:
		timer += delta
		return

	timer = 0.0

	var distance = camera.global_transform.origin.distance_to(global_transform.origin)
	# The LOD level to choose (lower is more detailed).
	# Multiply distance values by scale for relativism.
	if distance < lod_0_relative_distance * self.scale.length():
		# Show
		show_scenes(\"LOD0\")
		# Hide
		hide_scenes(\"LOD1\")
		hide_scenes(\"LOD2\")
		hide_scenes(\"LOD3\")
	elif distance < lod_1_relative_distance * self.scale.length():
		# Show
		show_scenes(\"LOD1\")
		# Hide
		hide_scenes(\"LOD2\")
		hide_scenes(\"LOD0\")
		hide_scenes(\"LOD3\")
	elif distance < lod_2_relative_distance * self.scale.length():
		# Show
		show_scenes(\"LOD2\")
		# Hide
		hide_scenes(\"LOD1\")
		hide_scenes(\"LOD0\")
		hide_scenes(\"LOD3\")
	else:
		# Show
		show_scenes(\"LOD3\")
		# Hide
		hide_scenes(\"LOD0\")
		hide_scenes(\"LOD1\")
		hide_scenes(\"LOD2\")
		

func show_scenes(lod_name):
	self.get_node(lod_name).get_node(\"Scene_items\").show()
			
func hide_scenes(lod_name):
	self.get_node(lod_name).get_node(\"Scene_items\").hide()
"

[node name="LODs" type="Spatial"]
script = SubResource( 1 )
__meta__ = {
"_editor_description_": "Do not rename or edit the nodes.
Put meshes into Scene Items nodes"
}

[node name="LOD0" type="Spatial" parent="."]
__meta__ = {
"_edit_lock_": true
}

[node name="Scene_items" type="Spatial" parent="LOD0"]
__meta__ = {
"_edit_lock_": true
}

[node name="LOD1" type="Spatial" parent="."]
__meta__ = {
"_edit_lock_": true
}

[node name="Scene_items" type="Spatial" parent="LOD1"]
__meta__ = {
"_edit_lock_": true
}

[node name="LOD2" type="Spatial" parent="."]
__meta__ = {
"_edit_lock_": true
}

[node name="Scene_items" type="Spatial" parent="LOD2"]
__meta__ = {
"_edit_lock_": true
}

[node name="LOD3" type="Spatial" parent="."]
__meta__ = {
"_edit_lock_": true
}

[node name="Scene_items" type="Spatial" parent="LOD3"]
__meta__ = {
"_edit_lock_": true
}

I'd prefer focusing on having people test #4 and make sure it works well in production, as it has a greater potential for performance optimization by using fewer script instances. Both your proposal and #4 will break compatibility, so it'll require bumping the version of this add-on to 2.0.0.

Note that LOD is slated to be implemented in 3.6 by godotengine/godot#53778, so this add-on may not be relevant anymore in the future.

I'd prefer focusing on having people test #4 and make sure it works well in production, as it has a greater potential for performance optimization by using fewer script instances. Both your proposal and #4 will break compatibility, so it'll require bumping the version of this add-on to 2.0.0.

Note that LOD is slated to be implemented in 3.6 by godotengine/godot#53778, so this add-on may not be relevant anymore in the future.

Oh, that would be great! Still might want to have a small wrapper script to automate LOD ranges absolute values (proportional to object scale).