Skip to content

Commit

Permalink
feat/WIP: add GameObjectClusterCollection logic
Browse files Browse the repository at this point in the history
basic functionality works: adding a GameObject to a GameObjectClusterCollection instantiates GameObjects at pre-defined locations in another GameObjectCollection.

includes some changes which were required for this to work:

- add CRS option to Map config (required for Pantelleria data, removes hardcode)
- update renderers in their own threads (allows updating multiple renderers at once, required for updating both the Cluster and the individual collections at the same time)
  • Loading branch information
LandscapeLab Office committed Dec 27, 2023
1 parent 086e283 commit d2e6359
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 63 deletions.
2 changes: 2 additions & 0 deletions Communication/CommunicationServer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var _message_stack = {}

# initialize the websocket server and listening for client connections
func _ready():
logger.info("Starting up ws server...")

self._ws_server = WebSocketServer.new()
# FIXME: Seems like this is not needed anymore?
# self._ws_server.bind_ip = Settings.get_setting("server", "bind_ip")
Expand Down
2 changes: 2 additions & 0 deletions Config/Settings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func load_settings():
_load_defaults()
_load_from_user_config()
_load_from_cl()

logger.info("Finished setting up configuration")


func _load_defaults():
Expand Down
6 changes: 6 additions & 0 deletions GameSystem/GameMode.gd
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func add_game_object_collection_for_feature_layer(collection_name, feature_layer
return collection


func add_cluster_game_object_collection(collection_name, feature_layer, location_layer, instance_goc):
var collection = GameObjectClusterCollection.new(collection_name, feature_layer, location_layer, instance_goc)
add_game_object_collection(collection)
return collection


func add_score(score: GameScore):
game_scores[score.name] = score
score.connect("value_changed",Callable(self,"_on_score_value_changed").bind(score))
Expand Down
30 changes: 23 additions & 7 deletions GameSystem/GameModesConfigurator.gd
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ func _load_game_modes(path: String, game_modes: Dictionary) -> void:
var game_mode = game_modes[key]

var game_mode_object = GameMode.new()
game_mode_object.extent = game_mode["Extent"]

if "Extent" in game_mode:
game_mode_object.extent = game_mode["Extent"]

var game_object_collections = game_mode["GameObjectCollections"]
_deserialize_object_colletion(game_mode_object, game_object_collections)
Expand Down Expand Up @@ -60,12 +62,26 @@ func _deserialize_object_colletion(game_mode: GameMode, game_object_collections:
var layer: RefCounted = Layers.get_geo_layer_by_name(layer_name)

var collection_object: GameObjectCollection
if layer is GeoFeatureLayer:
collection_object = \
game_mode.add_game_object_collection_for_feature_layer(collection_name, layer)
else:
# TODO: how to handle in case of GeoRasterLayer?
pass

var type = collection["type"] if "type" in collection else "GeoGameObjectCollection"

if type == "GeoGameObjectCollection":
collection_object = game_mode.add_game_object_collection_for_feature_layer(
collection_name, layer
)
elif type == "GameObjectClusterCollection":
var location_layer = LayerCompositionSerializer.get_feature_layer_from_string(
collection["location_layer"],
path
)
var instance_goc = game_mode.game_object_collections[collection["goc"]]

collection_object = game_mode.add_cluster_game_object_collection(
collection_name,
layer,
location_layer,
instance_goc
)


var mapping_type_to_construction_func = {
Expand Down
98 changes: 98 additions & 0 deletions GameSystem/GameObjectCollections/GameObjectClusterCollection.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
extends GameObjectCollection
class_name GameObjectClusterCollection

#
# A collection of clusters of GameObjects at pre-defined locations.
#

var attributes = {}
var feature_layer
var location_layer
var instance_goc

var cluster_size = 5
var game_object_instances = {}

signal game_object_added(new_game_object)
signal game_object_removed(removed_game_object)


func _init(initial_name, initial_feature_layer, initial_location_layer, initial_instance_goc):
super._init(initial_name)

feature_layer = initial_feature_layer
location_layer = initial_location_layer
instance_goc = initial_instance_goc

# Register all existing features
for feature in feature_layer.get_all_features():
_add_game_object(feature)

# Register future features automatically
feature_layer.connect("feature_added",Callable(self,"_add_game_object"))
feature_layer.connect("feature_removed",Callable(self,"_remove_game_object"))


func remove_nearby_game_objects(position, radius):
var features = feature_layer.get_features_near_position(
position.x,
position.z,
radius,
10000
)

for feature in features:
feature_layer.remove_feature(feature)


func _add_game_object(feature):
var game_object_for_feature = GameSystem.create_game_object_for_geo_feature(feature, self)
game_objects[game_object_for_feature.id] = game_object_for_feature

feature.connect("feature_changed",Callable(self,"_on_feature_changed").bind(feature))

emit_signal("game_object_added", game_object_for_feature)
emit_signal("changed")


func _on_feature_changed(feature):
# Activate locations
var feature_position = feature.get_vector3()
var location_features = location_layer.get_features_near_position(
feature_position.x,
-feature_position.z,
2000.0,
cluster_size
)
for location_feature in location_features:
var location = location_feature.get_vector3()
var new_location_feature = instance_goc.feature_layer.create_feature()
new_location_feature.set_vector3(location)
#var game_object_instance = GameSystem.create_game_object_for_geo_feature(location_feature, instance_goc)
#game_object_instances[game_object_instance.id] = game_object_instance

emit_signal("changed")


func _remove_game_object(feature):
# TODO: do this more elegantly without iterating over everything
# find corresponding object
var corresponding_game_object

for game_object in game_objects.values():
if game_object.geo_feature.get_id() == feature.get_id():
corresponding_game_object = game_object

if corresponding_game_object:
# TODO: Remove cluster instances

GameSystem.apply_game_object_removal(name, corresponding_game_object.id)

emit_signal("game_object_removed", corresponding_game_object)
emit_signal("changed")


func add_attribute_mapping(attribute):
attributes[attribute.name] = attribute
emit_signal("changed")

6 changes: 3 additions & 3 deletions GameSystem/GameSystem.gd
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ func create_new_game_object(collection, position := Vector3.ZERO):
# FIXME: This if should be removed, it's a hacky way to allow the PlayerGameObjectCollection to
# move the player checked "NEW_TOKEN" while allowing the actual creation of new objects in
# GeoGameObjectCollections
if is_instance_of(collection, GeoGameObjectCollection):
return create_new_geo_game_object(collection, position)
else:
if is_instance_of(collection, PlayerGameObjectCollection):
collection.game_objects.values()[0].set_position(position)
return collection.game_objects.values()[0]
else:
return create_new_geo_game_object(collection, position)


func create_new_geo_game_object(collection, position := Vector3.ZERO):
Expand Down
19 changes: 10 additions & 9 deletions Layers/Renderers/GeoLayer/GeoLayerRenderer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ var global_to_local_transform
var local_to_global_transform


func _ready():
global_to_local_transform = GeoTransform.new()
global_to_local_transform.set_transform(31287, 3857)

local_to_global_transform = GeoTransform.new()
local_to_global_transform.set_transform(3857, 31287)


# Overload with the functionality to load new data, but not use (visualize) it yet. Run in a thread,
# so watch out for thread safety!
func load_new_data():
Expand Down Expand Up @@ -59,13 +51,22 @@ func get_center_global():
return Vector2(transformed.x, transformed.z)


func set_metadata(new_center: Vector2, new_viewport_size: Vector2, new_zoom: Vector2):
func set_metadata(new_center: Vector2, new_viewport_size: Vector2, new_zoom: Vector2, crs_from: int):
center = new_center
viewport_size = new_viewport_size
zoom = new_zoom
# The maximum radius is at the corners => get the diagonale divided by 2
radius = sqrt(pow(new_viewport_size.x / new_zoom.x, 2)
+ pow(new_viewport_size.y / new_zoom.y, 2)) / 2

# The CRS shouldn't change at runtime, so we only need to do this once
if not global_to_local_transform:
global_to_local_transform = GeoTransform.new()
global_to_local_transform.set_transform(crs_from, 3857)

if not local_to_global_transform:
local_to_global_transform = GeoTransform.new()
local_to_global_transform.set_transform(3857, crs_from)


# Reload the data within this layer
Expand Down
77 changes: 47 additions & 30 deletions Layers/Renderers/GeoLayer/GeoLayerRenderers.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ extends Node2D
player_node = new_player
$PlayerSprite.visible = new_player != null
var geo_transform: GeoTransform
var loading_thread := Thread.new()
var loading_threads = {}
var raster_renderer = preload("res://Layers/Renderers/GeoLayer/GeoRasterLayerRenderer.tscn")
var feature_renderer = preload("res://Layers/Renderers/GeoLayer/GeoFeatureLayerRenderer.tscn")
var crs_from
signal loading_finished
signal loading_started
signal camera_extent_changed(new_camera_extent)
Expand Down Expand Up @@ -46,7 +47,9 @@ var offset := Vector2.ZERO :
camera_extent_changed.emit(camera_extent)


func setup(initial_center):
func setup(initial_center, initial_crs_from):
crs_from = initial_crs_from

camera.offset_changed.connect(apply_offset)

center = initial_center
Expand Down Expand Up @@ -106,14 +109,17 @@ func instantiate_layer_composition_renderer(lc_name: String):
geo_layer.feature_removed.connect(_on_feature_removed.bind(renderer), CONNECT_DEFERRED)

if renderer:
loading_threads[renderer] = Thread.new()

renderer.position = offset
renderer.name = lc_name
renderer.visibility_layer = visibility_layer

renderer.set_metadata(
center,
camera.get_viewport().size,
camera.zoom
camera.zoom,
crs_from
)

add_child(renderer)
Expand Down Expand Up @@ -143,14 +149,17 @@ func instantiate_geolayer_renderer(layer_name: String):
return

if renderer:
loading_threads[renderer] = Thread.new()

renderer.position = offset
renderer.name = geo_layer.get_file_info()["name"]
renderer.visibility_layer = visibility_layer

renderer.set_metadata(
center,
camera.get_viewport().size,
camera.zoom
camera.zoom,
crs_from
)

add_child(renderer)
Expand All @@ -174,43 +183,51 @@ func apply_offset(new_offset, new_viewport_size, new_zoom):
loading_started.emit()

# Start loading thread and load all geolayers in the thread
renderers_finished = 0
renderers_applied = 0

if load_data_threaded:
if loading_thread.is_started() and not loading_thread.is_alive():
loading_thread.wait_to_finish()

if not loading_thread.is_started():
renderers_finished = 0
renderers_applied = 0
loading_thread.start(update_renderers.bind(
center, new_offset, new_viewport_size, new_zoom), Thread.PRIORITY_HIGH)
for renderer in get_children():
if renderer is GeoLayerRenderer:
if loading_threads[renderer].is_started() and not loading_threads[renderer].is_alive():
loading_threads[renderer].wait_to_finish()
loading_threads[renderer].start(update_renderer_with_new_data.bind(
renderer, center, new_offset, new_viewport_size, new_zoom),
Thread.PRIORITY_HIGH)
else:
renderers_finished = 0
renderers_applied = 0
update_renderers(center, new_offset, new_viewport_size, new_zoom)
for renderer in get_children():
if renderer is GeoLayerRenderer:
update_renderer_with_new_data(renderer, center, new_offset, new_viewport_size, new_zoom)


func update_renderers(new_center, new_offset, new_viewport_size, new_zoom):
func update_renderer_with_new_data(renderer, new_center, new_offset, new_viewport_size, new_zoom):
Thread.set_thread_safety_checks_enabled(false)
# Now, load the data of each renderer
for renderer in get_children():
if renderer is GeoLayerRenderer:
renderer.set_metadata(
new_center,
new_viewport_size,
new_zoom
)
renderer.load_new_data()
_on_renderer_finished.call_deferred(renderer.name)

renderer.set_metadata(
new_center,
new_viewport_size,
new_zoom,
crs_from
)

renderer.load_new_data()
_on_renderer_finished.call_deferred(renderer.name)


func _on_feature_added(feature, renderer):
feature.feature_changed.connect(_on_feature_changed.bind(renderer))
update_renderer(renderer)


func _on_feature_removed(feature, renderer):
update_renderer(renderer)


func _on_feature_changed(renderer):
update_renderer(renderer)


func update_renderer_threaded(renderer):
Thread.set_thread_safety_checks_enabled(false)
renderer.load_new_data()
Expand All @@ -219,11 +236,11 @@ func update_renderer_threaded(renderer):

func update_renderer(renderer):
if load_data_threaded:
if loading_thread.is_started() and not loading_thread.is_alive():
loading_thread.wait_to_finish()
if loading_threads[renderer].is_started() and not loading_threads[renderer].is_alive():
loading_threads[renderer].wait_to_finish()

if not loading_thread.is_started():
loading_thread.start(update_renderer_threaded.bind(renderer), Thread.PRIORITY_NORMAL)
if not loading_threads[renderer].is_started():
loading_threads[renderer].start(update_renderer_threaded.bind(renderer), Thread.PRIORITY_NORMAL)
else:
renderer.load_new_data()
renderer.apply_new_data()
Expand Down
Loading

0 comments on commit d2e6359

Please sign in to comment.