Last active
March 6, 2024 12:58
-
-
Save btuso/ef3e34f39d1a0c5d39a2060a4d82bb18 to your computer and use it in GitHub Desktop.
View Distance Multi Mesh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends MultiMeshInstance | |
var ZERO_TRANSFORM = Transform.IDENTITY.scaled(Vector3.ZERO) | |
# Over how many frames to divide the whole multimesh scan | |
export var refresh_samples = 20 | |
# How many meshes to display at once inside the area | |
var display_instances = 3000 | |
# Allow the displayed meshes amount to adjust automatically | |
export var adjust_displayed_instances_amount = true | |
# How many frames to wait until adjusting the pool size | |
export var pool_sampling_rate = 60 | |
# How many instances to add to the pool when we need to grow it beyond it's avg size | |
export var pool_enlarge_amount = 100 | |
# Store mesh status | |
var amount_of_meshes | |
var visible_meshes = {} | |
var instance_mapping = {} | |
var transforms = [] | |
# Store pool status | |
var pool_excess_samples = 0 | |
var current_pool_excess = 0 | |
var cumulative_pool_size = 0 | |
var pool_size_samples = 0 | |
# Store refresh status | |
var should_refresh | |
var refresh_sample = 0 | |
var display_area | |
func _ready(): | |
amount_of_meshes = multimesh.instance_count | |
# Store transformations | |
for i in amount_of_meshes: | |
transforms.append(multimesh.get_instance_transform(i)) | |
instance_mapping[i] = -1 | |
visible_meshes[i] = false | |
# Hide meshes | |
_hide_all() | |
func _hide_all(): | |
for i in multimesh.instance_count: | |
multimesh.set_instance_transform(i, ZERO_TRANSFORM) | |
visible_meshes[i] = false | |
# Called by signal, expect to collide with an cylinder area | |
func _player_entered_mesh_area(object): | |
display_area = object | |
should_refresh = true | |
# Called by signal | |
func _player_left(object): | |
should_refresh = false | |
display_area = null | |
_hide_all() | |
func _process(delta): | |
if should_refresh: | |
# Expect a cylinder area | |
var shape = display_area.shape_owner_get_shape(0,0) | |
var origin = display_area.global_transform.origin | |
_show_in_circle(origin, shape.radius, refresh_sample) | |
refresh_sample += 1 | |
if refresh_sample >= refresh_samples: | |
refresh_sample = 0 | |
func _show_in_circle(origin, radius, batch_number): | |
var center = Vector2(origin.x, origin.z) | |
var radius_squared = radius * radius | |
# Collect instances that are free to be used | |
var available_instances = [] | |
var pool_index = 0 | |
for i in display_instances: | |
if multimesh.get_instance_transform(i) == ZERO_TRANSFORM: | |
# Instance was not being shown | |
available_instances.append(i) | |
continue | |
var transform_origin = multimesh.get_instance_transform(i).origin | |
var point = Vector2(transform_origin.x, transform_origin.z) | |
if point.distance_squared_to(center) > radius_squared: | |
# Instance is outside area and should be hidden | |
visible_meshes[instance_mapping[i]] = false | |
available_instances.append(i) | |
# Calculate how many posible transforms to go over this frame | |
var batch_size = (amount_of_meshes / (refresh_samples + 1)) | |
var batch_start = batch_size * batch_number | |
var batch_end = batch_start + batch_size | |
# Show the instances that are inside the area | |
for i in range(batch_start, batch_end): | |
if pool_index == available_instances.size(): | |
# no more instances available | |
break | |
if visible_meshes[i]: | |
# we're already showing that mesh with some instance | |
continue | |
var transform_origin = transforms[i].origin | |
var point = Vector2(transform_origin.x, transform_origin.z) | |
if point.distance_squared_to(center) <= radius_squared: | |
# The mesh should be shown | |
multimesh.set_instance_transform(available_instances[pool_index], transforms[i]) | |
visible_meshes[i] = true | |
instance_mapping[available_instances[pool_index]] = i | |
pool_index += 1 | |
# Hide the instances that are no longer in view and weren't recycled this frame | |
for i in range(pool_index, available_instances.size()): | |
if multimesh.get_instance_transform(available_instances[i]) != ZERO_TRANSFORM: | |
multimesh.set_instance_transform(available_instances[i], ZERO_TRANSFORM) | |
# If we have free instances at the end of the frame, it means we went over | |
# every posible mesh transform in the multimesh. We want to be a little bit | |
# below that number, so that we can take advantage of early loop breaking. | |
if adjust_displayed_instances_amount: | |
_resize_instance_pool(display_instances, available_instances.size() - pool_index) | |
func _resize_instance_pool(current_pool_size, unused_instances): | |
# Keep track of average pool size and unused instances | |
pool_size_samples += 1 | |
cumulative_pool_size += current_pool_size | |
pool_excess_samples += 1 | |
current_pool_excess += unused_instances | |
# Only resize every now and then | |
if pool_excess_samples >= pool_sampling_rate: | |
var avg_excess = current_pool_excess / pool_excess_samples | |
var excess_to_remove = ceil(avg_excess * 0.5) | |
if excess_to_remove > 0: | |
# If we need to remove instances then do so | |
display_instances -= excess_to_remove | |
else: | |
# If there's no need to remove instances, check if we need to add some | |
var avg_pool_size = cumulative_pool_size / pool_size_samples | |
if display_instances >= avg_pool_size: | |
# We're over pool size average and need to add more instances | |
display_instances += pool_enlarge_amount | |
else: | |
# We're below pool size average and need to add more instances | |
display_instances += ceil((avg_pool_size - display_instances) * 0.5) | |
print(str(display_instances)) | |
# Reset excess tracker | |
pool_excess_samples = 0 | |
current_pool_excess = 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment